summaryrefslogtreecommitdiff
path: root/chromium/storage
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-17 13:57:45 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-19 13:44:40 +0000
commit6ec7b8da05d21a3878bd21c691b41e675d74bb1c (patch)
treeb87f250bc19413750b9bb9cdbf2da20ef5014820 /chromium/storage
parentec02ee4181c49b61fce1c8fb99292dbb8139cc90 (diff)
downloadqtwebengine-chromium-6ec7b8da05d21a3878bd21c691b41e675d74bb1c.tar.gz
BASELINE: Update Chromium to 60.0.3112.70
Change-Id: I9911c2280a014d4632f254857876a395d4baed2d Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/storage')
-rw-r--r--chromium/storage/BUILD.gn11
-rw-r--r--chromium/storage/browser/BUILD.gn52
-rw-r--r--chromium/storage/browser/blob/OWNERS3
-rw-r--r--chromium/storage/browser/blob/README.md22
-rw-r--r--chromium/storage/browser/blob/blob_data_handle.cc12
-rw-r--r--chromium/storage/browser/blob/blob_memory_controller.cc93
-rw-r--r--chromium/storage/browser/blob/blob_memory_controller.h19
-rw-r--r--chromium/storage/browser/blob/blob_memory_controller_unittest.cc49
-rw-r--r--chromium/storage/browser/blob/blob_reader.cc7
-rw-r--r--chromium/storage/browser/blob/blob_url_request_job_factory.cc33
-rw-r--r--chromium/storage/browser/blob/blob_url_request_job_unittest.cc5
-rw-r--r--chromium/storage/browser/blob/shareable_file_reference.cc6
-rw-r--r--chromium/storage/browser/blob/view_blob_internals_job.cc50
-rw-r--r--chromium/storage/browser/blob/view_blob_internals_job.h3
-rw-r--r--chromium/storage/browser/database/database_quota_client.cc2
-rw-r--r--chromium/storage/browser/database/database_tracker_unittest.cc818
-rw-r--r--chromium/storage/browser/fileapi/copy_or_move_file_validator_unittest.cc337
-rw-r--r--chromium/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc878
-rw-r--r--chromium/storage/browser/fileapi/dragged_file_util_unittest.cc554
-rw-r--r--chromium/storage/browser/fileapi/file_system_context.cc18
-rw-r--r--chromium/storage/browser/fileapi/file_system_context.h8
-rw-r--r--chromium/storage/browser/fileapi/file_system_context_unittest.cc392
-rw-r--r--chromium/storage/browser/fileapi/file_system_dir_url_request_job_unittest.cc459
-rw-r--r--chromium/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc275
-rw-r--r--chromium/storage/browser/fileapi/file_system_operation_impl_unittest.cc1313
-rw-r--r--chromium/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc325
-rw-r--r--chromium/storage/browser/fileapi/file_system_quota_client_unittest.cc572
-rw-r--r--chromium/storage/browser/fileapi/file_system_url_request_job.cc6
-rw-r--r--chromium/storage/browser/fileapi/file_system_url_request_job.h1
-rw-r--r--chromium/storage/browser/fileapi/file_system_url_request_job_unittest.cc487
-rw-r--r--chromium/storage/browser/fileapi/file_system_usage_cache.cc42
-rw-r--r--chromium/storage/browser/fileapi/file_system_usage_cache.h2
-rw-r--r--chromium/storage/browser/fileapi/file_writer_delegate_unittest.cc516
-rw-r--r--chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc1
-rw-r--r--chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc1
-rw-r--r--chromium/storage/browser/fileapi/local_file_util_unittest.cc382
-rw-r--r--chromium/storage/browser/fileapi/obfuscated_file_util_unittest.cc2584
-rw-r--r--chromium/storage/browser/fileapi/plugin_private_file_system_backend.cc12
-rw-r--r--chromium/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc288
-rw-r--r--chromium/storage/browser/fileapi/quota/quota_backend_impl.cc16
-rw-r--r--chromium/storage/browser/fileapi/recursive_operation_delegate_unittest.cc389
-rw-r--r--chromium/storage/browser/fileapi/sandbox_directory_database_unittest.cc677
-rw-r--r--chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc18
-rw-r--r--chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc88
-rw-r--r--chromium/storage/browser/fileapi/sandbox_file_system_backend_unittest.cc311
-rw-r--r--chromium/storage/browser/fileapi/sandbox_origin_database_unittest.cc309
-rw-r--r--chromium/storage/browser/fileapi/sandbox_quota_observer.cc6
-rw-r--r--chromium/storage/browser/fileapi/task_runner_bound_observer_list.h2
-rw-r--r--chromium/storage/browser/fileapi/timed_task_helper.cc6
-rw-r--r--chromium/storage/browser/fileapi/timed_task_helper_unittest.cc1
-rw-r--r--chromium/storage/browser/fileapi/transient_file_util_unittest.cc123
-rw-r--r--chromium/storage/browser/quota/OWNERS3
-rw-r--r--chromium/storage/browser/quota/quota_database_unittest.cc698
-rw-r--r--chromium/storage/browser/quota/quota_manager.cc3
-rw-r--r--chromium/storage/browser/quota/quota_manager_proxy.cc2
-rw-r--r--chromium/storage/browser/quota/quota_manager_unittest.cc2271
-rw-r--r--chromium/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc402
-rw-r--r--chromium/storage/browser/quota/storage_monitor_unittest.cc706
-rw-r--r--chromium/storage/browser/quota/usage_tracker_unittest.cc339
-rw-r--r--chromium/storage/common/BUILD.gn20
-rw-r--r--chromium/storage/common/blob_storage/OWNERS3
-rw-r--r--chromium/storage/common/blob_storage/blob_storage_constants.h17
-rw-r--r--chromium/storage/common/database/OWNERS2
-rw-r--r--chromium/storage/common/database/database_connections_unittest.cc129
-rw-r--r--chromium/storage/common/database/database_identifier_unittest.cc287
-rw-r--r--chromium/storage/common/fileapi/file_system_util_unittest.cc310
-rw-r--r--chromium/storage/common/quota/OWNERS2
-rw-r--r--chromium/storage/common/storage_histograms.cc21
-rw-r--r--chromium/storage/common/storage_histograms.h18
69 files changed, 17670 insertions, 147 deletions
diff --git a/chromium/storage/BUILD.gn b/chromium/storage/BUILD.gn
new file mode 100644
index 00000000000..d6208ef440c
--- /dev/null
+++ b/chromium/storage/BUILD.gn
@@ -0,0 +1,11 @@
+# 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.
+import("//testing/test.gni")
+
+test("storage_unittests") {
+ deps = [
+ "//storage/browser:unittests",
+ "//storage/common:unittests",
+ ]
+}
diff --git a/chromium/storage/browser/BUILD.gn b/chromium/storage/browser/BUILD.gn
index 4fdb007f1de..c02bbeaae82 100644
--- a/chromium/storage/browser/BUILD.gn
+++ b/chromium/storage/browser/BUILD.gn
@@ -216,13 +216,15 @@ executable("dump_file_system") {
deps = [
":browser",
"//base",
- "//build/config/sanitizers:deps",
+ "//build/config:exe_and_shlib_deps",
"//build/win:default_exe_manifest",
"//storage/common",
]
}
-test("storage_unittests") {
+source_set("unittests") {
+ testonly = true
+
sources = [
# This target is in the process of being populated. See
# http://crbug.com/653751
@@ -236,20 +238,47 @@ test("storage_unittests") {
"blob/blob_transport_request_builder_unittest.cc",
"blob/blob_url_request_job_unittest.cc",
"database/database_quota_client_unittest.cc",
+ "database/database_tracker_unittest.cc",
"database/database_util_unittest.cc",
"database/databases_table_unittest.cc",
+ "fileapi/copy_or_move_file_validator_unittest.cc",
+ "fileapi/copy_or_move_operation_delegate_unittest.cc",
+ "fileapi/dragged_file_util_unittest.cc",
"fileapi/external_mount_points_unittest.cc",
+ "fileapi/file_system_context_unittest.cc",
+ "fileapi/file_system_dir_url_request_job_unittest.cc",
+ "fileapi/file_system_file_stream_reader_unittest.cc",
+ "fileapi/file_system_operation_impl_unittest.cc",
+ "fileapi/file_system_operation_impl_write_unittest.cc",
+ "fileapi/file_system_quota_client_unittest.cc",
+ "fileapi/file_system_url_request_job_unittest.cc",
"fileapi/file_system_url_unittest.cc",
"fileapi/file_system_usage_cache_unittest.cc",
+ "fileapi/file_writer_delegate_unittest.cc",
"fileapi/isolated_context_unittest.cc",
"fileapi/local_file_stream_reader_unittest.cc",
"fileapi/local_file_stream_writer_unittest.cc",
+ "fileapi/local_file_util_unittest.cc",
"fileapi/native_file_util_unittest.cc",
+ "fileapi/obfuscated_file_util_unittest.cc",
+ "fileapi/plugin_private_file_system_backend_unittest.cc",
"fileapi/quota/quota_backend_impl_unittest.cc",
"fileapi/quota/quota_reservation_manager_unittest.cc",
+ "fileapi/recursive_operation_delegate_unittest.cc",
+ "fileapi/sandbox_directory_database_unittest.cc",
+ "fileapi/sandbox_file_system_backend_delegate_unittest.cc",
+ "fileapi/sandbox_file_system_backend_unittest.cc",
"fileapi/sandbox_isolated_origin_database_unittest.cc",
+ "fileapi/sandbox_origin_database_unittest.cc",
"fileapi/sandbox_prioritized_origin_database_unittest.cc",
"fileapi/timed_task_helper_unittest.cc",
+ "fileapi/transient_file_util_unittest.cc",
+ "quota/quota_database_unittest.cc",
+ "quota/quota_manager_unittest.cc",
+ "quota/quota_temporary_storage_evictor_unittest.cc",
+ "quota/storage_monitor_unittest.cc",
+ "quota/usage_tracker_unittest.cc",
+ "test/mock_quota_manager_unittest.cc",
]
deps = [
@@ -270,8 +299,26 @@ static_library("test_support") {
sources = [
"test/async_file_test_helper.cc",
"test/async_file_test_helper.h",
+ "test/fileapi_test_file_set.cc",
+ "test/fileapi_test_file_set.h",
+ "test/mock_blob_url_request_context.cc",
+ "test/mock_blob_url_request_context.h",
+ "test/mock_file_change_observer.cc",
+ "test/mock_file_change_observer.h",
+ "test/mock_file_update_observer.cc",
+ "test/mock_file_update_observer.h",
+ "test/mock_quota_manager.cc",
+ "test/mock_quota_manager.h",
+ "test/mock_quota_manager_proxy.cc",
+ "test/mock_quota_manager_proxy.h",
"test/mock_special_storage_policy.cc",
"test/mock_special_storage_policy.h",
+ "test/mock_storage_client.cc",
+ "test/mock_storage_client.h",
+ "test/sandbox_database_test_helper.cc",
+ "test/sandbox_database_test_helper.h",
+ "test/sandbox_file_system_test_helper.cc",
+ "test/sandbox_file_system_test_helper.h",
"test/test_file_system_backend.cc",
"test/test_file_system_backend.h",
"test/test_file_system_context.cc",
@@ -285,5 +332,6 @@ static_library("test_support") {
"//base/test:test_support",
"//net:test_support",
"//testing/gtest",
+ "//third_party/leveldatabase",
]
}
diff --git a/chromium/storage/browser/blob/OWNERS b/chromium/storage/browser/blob/OWNERS
index 04a70ee3383..bdfd48d14e5 100644
--- a/chromium/storage/browser/blob/OWNERS
+++ b/chromium/storage/browser/blob/OWNERS
@@ -1,2 +1,5 @@
dmurph@chromium.org
jianli@chromium.org
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>FileAPI
diff --git a/chromium/storage/browser/blob/README.md b/chromium/storage/browser/blob/README.md
index 73ef187d07e..e0f9b556176 100644
--- a/chromium/storage/browser/blob/README.md
+++ b/chromium/storage/browser/blob/README.md
@@ -82,12 +82,14 @@ https://cs.chromium.org/chromium/src/storage/browser/blob/blob_memory_controller
**In-Memory Storage Limit**
* If the architecture is x64 and NOT Chrome OS or Android: `2GB`
-* Otherwise: `total_physical_memory / 5`
+* If Chrome OS: `total_physical_memory / 5`
+* If Android: `total_physical_memory / 100`
+
**Disk Storage Limit**
* If Chrome OS: `disk_size / 2`
-* If Android: `disk_size / 20`
+* If Android: `6 * disk_size / 100`
* Else: `disk_size / 10`
Note: Chrome OS's disk is part of the user partition, which is separate from the
@@ -102,16 +104,14 @@ we use is:
## Example Limits
-(All sizes in GB)
-
| Device | Ram | In-Memory Limit | Disk | Disk Limit | Min Disk Availability |
| --- | --- | --- | --- | --- | --- |
-| Cast | 0.5 | 0.1 | 0 | 0 | 0 |
-| Android Minimal | 0.5 | 0.1 | 8 | 0.4 | 0.2 |
-| Android Fat | 2 | 0.4 | 32 | 1.5 | 0.8 |
-| CrOS | 2 | 0.4 | 8 | 4 | 0.8 |
-| Desktop 32 | 3 | 0.6 | 500 | 50 | 1.2 |
-| Desktop 64 | 4 | 2 | 500 | 50 | 4 |
+| Cast | 512 MB | 102 MB | 0 | 0 | 0 |
+| Android Minimal | 512 MB | 5 MB | 8 GB | 491 MB | 10 MB |
+| Android Fat | 2 GB | 20 MB | 32 GB | 1.9 GB | 40 MB |
+| CrOS | 2 GB | 409 MB | 8 GB | 4 GB | 0.8 GB |
+| Desktop 32 | 3 GB | 614 MB | 500 GB | 50 GB | 1.2 GB |
+| Desktop 64 | 4 GB | 2 GB | 500 GB | 50 GB | 4 GB |
# Common Pitfalls
@@ -128,7 +128,7 @@ the renderer can get rid of the data.
## Leaking Blob References
If the blob object in Javascript is kept around, then the data will never be
-cleaned up in the backend. This will unnecessarily us memory, so make sure to
+cleaned up in the backend. This will unnecessarily use memory, so make sure to
dereference blob objects if they are no longer needed.
Similarily if a URL is created for a blob, this will keep the blob data around
diff --git a/chromium/storage/browser/blob/blob_data_handle.cc b/chromium/storage/browser/blob/blob_data_handle.cc
index 8d29cca76f3..0ba7b360f2a 100644
--- a/chromium/storage/browser/blob/blob_data_handle.cc
+++ b/chromium/storage/browser/blob/blob_data_handle.cc
@@ -102,13 +102,13 @@ BlobDataHandle::BlobDataHandle(const std::string& uuid,
size,
context)) {
DCHECK(io_task_runner_.get());
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
}
BlobDataHandle::BlobDataHandle(const BlobDataHandle& other) = default;
BlobDataHandle::~BlobDataHandle() {
- if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ if (!io_task_runner_->RunsTasksInCurrentSequence()) {
BlobDataHandleShared* raw = shared_.get();
raw->AddRef();
shared_ = nullptr;
@@ -120,14 +120,14 @@ BlobDataHandle& BlobDataHandle::operator=(
const BlobDataHandle& other) = default;
bool BlobDataHandle::IsBeingBuilt() const {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (!shared_->context_)
return false;
return BlobStatusIsPending(GetBlobStatus());
}
bool BlobDataHandle::IsBroken() const {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (!shared_->context_)
return true;
return BlobStatusIsError(GetBlobStatus());
@@ -138,7 +138,7 @@ BlobStatus BlobDataHandle::GetBlobStatus() const {
}
void BlobDataHandle::RunOnConstructionComplete(const BlobStatusCallback& done) {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (!shared_->context_.get()) {
done.Run(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
return;
@@ -147,7 +147,7 @@ void BlobDataHandle::RunOnConstructionComplete(const BlobStatusCallback& done) {
}
std::unique_ptr<BlobDataSnapshot> BlobDataHandle::CreateSnapshot() const {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (!shared_->context_.get())
return nullptr;
return shared_->context_->CreateSnapshot(shared_->uuid_);
diff --git a/chromium/storage/browser/blob/blob_memory_controller.cc b/chromium/storage/browser/blob/blob_memory_controller.cc
index 298037cc5ee..27370be1c3f 100644
--- a/chromium/storage/browser/blob/blob_memory_controller.cc
+++ b/chromium/storage/browser/blob/blob_memory_controller.cc
@@ -16,6 +16,7 @@
#include "base/guid.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
@@ -41,6 +42,7 @@ namespace storage {
namespace {
constexpr int64_t kUnknownDiskAvailability = -1ll;
constexpr uint64_t kMegabyte = 1024ull * 1024;
+const int64_t kMinSecondsForPressureEvictions = 30;
using FileCreationInfo = BlobMemoryController::FileCreationInfo;
using MemoryAllocation = BlobMemoryController::MemoryAllocation;
@@ -53,8 +55,8 @@ using DiskSpaceFuncPtr = BlobMemoryController::DiskSpaceFuncPtr;
// Note: The disk is the user partition, so the operating system can still
// function if this is full.
// Android:
-// * RAM - 20%
-// * Disk - 5%
+// * RAM - 1%
+// * Disk - 6%
// Desktop:
// * Ram - 20%, or 2 GB if x64.
// * Disk - 10%
@@ -71,6 +73,8 @@ BlobStorageLimits CalculateBlobStorageLimitsImpl(const FilePath& storage_dir,
#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)
constexpr size_t kTwoGigabytes = 2ull * 1024 * 1024 * 1024;
limits.max_blob_in_memory_space = kTwoGigabytes;
+#elif defined(OS_ANDROID)
+ limits.max_blob_in_memory_space = static_cast<size_t>(memory_size / 100ll);
#else
limits.max_blob_in_memory_space = static_cast<size_t>(memory_size / 5ll);
#endif
@@ -81,7 +85,7 @@ BlobStorageLimits CalculateBlobStorageLimitsImpl(const FilePath& storage_dir,
#if defined(OS_CHROMEOS)
limits.desired_max_disk_space = static_cast<uint64_t>(disk_size / 2ll);
#elif defined(OS_ANDROID)
- limits.desired_max_disk_space = static_cast<uint64_t>(disk_size / 20ll);
+ limits.desired_max_disk_space = static_cast<uint64_t>(3ll * disk_size / 50);
#else
limits.desired_max_disk_space = static_cast<uint64_t>(disk_size / 10ll);
#endif
@@ -245,7 +249,7 @@ uint64_t GetTotalSizeAndFileSizes(
unreserved_file_items,
std::vector<uint64_t>* file_sizes_output) {
uint64_t total_size_output = 0;
- base::SmallMap<std::map<uint64_t, uint64_t>> file_id_to_sizes;
+ base::small_map<std::map<uint64_t, uint64_t>> file_id_to_sizes;
for (const auto& item : unreserved_file_items) {
const DataElement& element = item->item()->data_element();
uint64_t file_id = BlobDataBuilder::GetFutureFileID(element);
@@ -503,6 +507,9 @@ BlobMemoryController::BlobMemoryController(
disk_space_function_(&base::SysInfo::AmountOfFreeDiskSpace),
populated_memory_items_(
base::MRUCache<uint64_t, ShareableBlobDataItem*>::NO_AUTO_EVICT),
+ memory_pressure_listener_(
+ base::Bind(&BlobMemoryController::OnMemoryPressure,
+ base::Unretained(this))),
weak_factory_(this) {}
BlobMemoryController::~BlobMemoryController() {}
@@ -602,7 +609,8 @@ base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveMemoryQuota(
if (total_bytes_needed <= GetAvailableMemoryForBlobs()) {
GrantMemoryAllocations(&unreserved_memory_items,
static_cast<size_t>(total_bytes_needed));
- MaybeScheduleEvictionUntilSystemHealthy();
+ MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
done_callback.Run(true);
return base::WeakPtr<QuotaAllocationTask>();
}
@@ -613,7 +621,8 @@ base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveMemoryQuota(
auto weak_ptr = AppendMemoryTask(
total_bytes_needed, std::move(unreserved_memory_items), done_callback);
- MaybeScheduleEvictionUntilSystemHealthy();
+ MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
return weak_ptr;
}
@@ -647,7 +656,8 @@ void BlobMemoryController::NotifyMemoryItemsUsed(
populated_memory_items_.Put(item->item_id(), item.get());
}
}
- MaybeScheduleEvictionUntilSystemHealthy();
+ MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
}
void BlobMemoryController::CalculateBlobStorageLimits() {
@@ -769,11 +779,12 @@ void BlobMemoryController::MaybeGrantPendingMemoryRequests() {
}
size_t BlobMemoryController::CollectItemsForEviction(
- std::vector<scoped_refptr<ShareableBlobDataItem>>* output) {
+ std::vector<scoped_refptr<ShareableBlobDataItem>>* output,
+ uint64_t min_page_file_size) {
base::CheckedNumeric<size_t> total_items_size = 0;
// Process the recent item list and remove items until we have at least a
// minimum file size or we're at the end of our items to page to disk.
- while (total_items_size.ValueOrDie() < limits_.min_page_file_size &&
+ while (total_items_size.ValueOrDie() < min_page_file_size &&
!populated_memory_items_.empty()) {
auto iterator = --populated_memory_items_.end();
ShareableBlobDataItem* item = iterator->second;
@@ -787,7 +798,8 @@ size_t BlobMemoryController::CollectItemsForEviction(
return total_items_size.ValueOrDie();
}
-void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy() {
+void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
// Don't do eviction when others are happening, as we don't change our
// pending_memory_quota_total_size_ value until after the paging files have
// been written.
@@ -798,20 +810,42 @@ void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy() {
static_cast<uint64_t>(pending_memory_quota_total_size_) +
blob_memory_used_;
+ size_t in_memory_limit = limits_.memory_limit_before_paging();
+ uint64_t min_page_file_size = limits_.min_page_file_size;
+ if (memory_pressure_level !=
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
+ in_memory_limit = 0;
+ // Use lower page file size to reduce using more memory for writing under
+ // pressure.
+ min_page_file_size = limits_.max_blob_in_memory_space *
+ limits_.max_blob_in_memory_space_under_pressure_ratio;
+ }
+
// We try to page items to disk until our current system size + requested
// memory is below our size limit.
// Size limit is a lower |memory_limit_before_paging()| if we have disk space.
while (total_memory_usage > limits_.effective_max_disk_space ||
(disk_used_ < limits_.effective_max_disk_space &&
- total_memory_usage > limits_.memory_limit_before_paging())) {
+ total_memory_usage > in_memory_limit)) {
+ const char* reason = nullptr;
+ if (memory_pressure_level !=
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
+ reason = "OnMemoryPressure";
+ } else if (total_memory_usage > limits_.effective_max_disk_space) {
+ reason = "SizeExceededMaxDiskSpace";
+ } else {
+ reason = "SizeExceededInMemoryLimit";
+ }
+
// We only page when we have enough items to fill a whole page file.
- if (populated_memory_items_bytes_ < limits_.min_page_file_size)
+ if (populated_memory_items_bytes_ < min_page_file_size)
break;
- DCHECK_LE(limits_.min_page_file_size,
- static_cast<uint64_t>(blob_memory_used_));
+ DCHECK_LE(min_page_file_size, static_cast<uint64_t>(blob_memory_used_));
std::vector<scoped_refptr<ShareableBlobDataItem>> items_to_swap;
- size_t total_items_size = CollectItemsForEviction(&items_to_swap);
+
+ size_t total_items_size =
+ CollectItemsForEviction(&items_to_swap, min_page_file_size);
if (total_items_size == 0)
break;
@@ -847,7 +881,10 @@ void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy() {
total_items_size),
base::Bind(&BlobMemoryController::OnEvictionComplete,
weak_factory_.GetWeakPtr(), base::Passed(&file_reference),
- base::Passed(&items_to_swap), total_items_size));
+ base::Passed(&items_to_swap), total_items_size, reason,
+ total_memory_usage));
+
+ last_eviction_time_ = base::TimeTicks::Now();
}
RecordTracingCounters();
}
@@ -856,6 +893,8 @@ void BlobMemoryController::OnEvictionComplete(
scoped_refptr<ShareableFileReference> file_reference,
std::vector<scoped_refptr<ShareableBlobDataItem>> items,
size_t total_items_size,
+ const char* evict_reason,
+ size_t memory_usage_before_eviction,
std::pair<FileCreationInfo, int64_t /* avail_disk */> result) {
if (!file_paging_enabled_)
return;
@@ -891,12 +930,32 @@ void BlobMemoryController::OnEvictionComplete(
}
in_flight_memory_used_ -= total_items_size;
+ // Record change in memory usage at the last eviction reply.
+ size_t total_usage = blob_memory_used_ + pending_memory_quota_total_size_;
+ if (!pending_evictions_ && memory_usage_before_eviction >= total_usage) {
+ std::string full_histogram_name =
+ std::string("Storage.Blob.SizeEvictedToDiskInKB.") + evict_reason;
+ base::UmaHistogramCounts100000(
+ full_histogram_name,
+ (memory_usage_before_eviction - total_usage) / 1024);
+ }
+
// We want callback on blobs up to the amount we've freed.
MaybeGrantPendingMemoryRequests();
// If we still have more blobs waiting and we're not waiting on more paging
// operations, schedule more.
- MaybeScheduleEvictionUntilSystemHealthy();
+ MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
+}
+
+void BlobMemoryController::OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ auto time_from_last_evicion = base::TimeTicks::Now() - last_eviction_time_;
+ if (time_from_last_evicion.InSeconds() < kMinSecondsForPressureEvictions)
+ return;
+
+ MaybeScheduleEvictionUntilSystemHealthy(memory_pressure_level);
}
FilePath BlobMemoryController::GenerateNextPageFileName() {
diff --git a/chromium/storage/browser/blob/blob_memory_controller.h b/chromium/storage/browser/blob/blob_memory_controller.h
index eb29518a15e..7c72d0f9a1a 100644
--- a/chromium/storage/browser/blob/blob_memory_controller.h
+++ b/chromium/storage/browser/blob/blob_memory_controller.h
@@ -21,7 +21,9 @@
#include "base/containers/mru_cache.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
#include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
@@ -178,6 +180,7 @@ class STORAGE_EXPORT BlobMemoryController {
class FileQuotaAllocationTask;
class MemoryQuotaAllocationTask;
+ FRIEND_TEST_ALL_PREFIXES(BlobMemoryControllerTest, OnMemoryPressure);
// So this (and only this) class can call CalculateBlobStorageLimits().
friend class content::ChromeBlobStorageContext;
@@ -205,10 +208,12 @@ class STORAGE_EXPORT BlobMemoryController {
void MaybeGrantPendingMemoryRequests();
size_t CollectItemsForEviction(
- std::vector<scoped_refptr<ShareableBlobDataItem>>* output);
+ std::vector<scoped_refptr<ShareableBlobDataItem>>* output,
+ uint64_t min_page_file_size);
// Schedule paging until our memory usage is below our memory limit.
- void MaybeScheduleEvictionUntilSystemHealthy();
+ void MaybeScheduleEvictionUntilSystemHealthy(
+ base::MemoryPressureListener::MemoryPressureLevel level);
// Called when we've completed evicting a list of items to disk. This is where
// we swap the bytes items for file items, and update our bookkeeping.
@@ -216,7 +221,12 @@ class STORAGE_EXPORT BlobMemoryController {
scoped_refptr<ShareableFileReference> file_reference,
std::vector<scoped_refptr<ShareableBlobDataItem>> items,
size_t total_items_size,
- std::pair<FileCreationInfo, int64_t /* disk_avail */> result);
+ const char* evict_reason,
+ size_t memory_usage_before_eviction,
+ std::pair<FileCreationInfo, int64_t /* avail_disk */> result);
+
+ void OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
size_t GetAvailableMemoryForBlobs() const;
uint64_t GetAvailableFileSpaceForBlobs() const;
@@ -265,6 +275,7 @@ class STORAGE_EXPORT BlobMemoryController {
scoped_refptr<base::TaskRunner> file_runner_;
// This defaults to calling base::SysInfo::AmountOfFreeDiskSpace.
DiskSpaceFuncPtr disk_space_function_;
+ base::TimeTicks last_eviction_time_;
// Lifetime of the ShareableBlobDataItem objects is handled externally in the
// BlobStorageContext class.
@@ -275,6 +286,8 @@ class STORAGE_EXPORT BlobMemoryController {
// item to the recent_item_cache_ above.
std::unordered_set<uint64_t> items_paging_to_file_;
+ base::MemoryPressureListener memory_pressure_listener_;
+
base::WeakPtrFactory<BlobMemoryController> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(BlobMemoryController);
diff --git a/chromium/storage/browser/blob/blob_memory_controller_unittest.cc b/chromium/storage/browser/blob/blob_memory_controller_unittest.cc
index 918315b378e..2ab02f0ea86 100644
--- a/chromium/storage/browser/blob/blob_memory_controller_unittest.cc
+++ b/chromium/storage/browser/blob/blob_memory_controller_unittest.cc
@@ -31,6 +31,7 @@ const std::string kBlobStorageDirectory = "blob_storage";
const size_t kTestBlobStorageIPCThresholdBytes = 20;
const size_t kTestBlobStorageMaxSharedMemoryBytes = 50;
const size_t kTestBlobStorageMaxBlobMemorySize = 500;
+const float kTestMaxBlobInMemorySpaceUnderPressureRatio = 0.004f;
const uint64_t kTestBlobStorageMaxDiskSpace = 1000;
const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
@@ -88,6 +89,8 @@ class BlobMemoryControllerTest : public testing::Test {
limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize;
+ limits.max_blob_in_memory_space_under_pressure_ratio =
+ kTestMaxBlobInMemorySpaceUnderPressureRatio;
limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes;
@@ -100,6 +103,8 @@ class BlobMemoryControllerTest : public testing::Test {
limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize;
+ limits.max_blob_in_memory_space_under_pressure_ratio =
+ kTestMaxBlobInMemorySpaceUnderPressureRatio;
limits.desired_max_disk_space = kTestSmallBlobStorageMaxDiskSpace;
limits.effective_max_disk_space = kTestSmallBlobStorageMaxDiskSpace;
limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes;
@@ -1120,4 +1125,48 @@ TEST_F(BlobMemoryControllerTest, DiskSpaceUnknown) {
EXPECT_FALSE(controller.limits().IsDiskSpaceConstrained());
}
+TEST_F(BlobMemoryControllerTest, OnMemoryPressure) {
+ BlobMemoryController controller(temp_dir_.GetPath(), file_runner_);
+ SetTestMemoryLimits(&controller);
+ AssertEnoughDiskSpace();
+
+ char kData[1];
+ kData[0] = 'e';
+
+ std::vector<scoped_refptr<ShareableBlobDataItem>> small_items;
+ size_t size_to_load = 2 * kTestBlobStorageMaxBlobMemorySize *
+ kTestMaxBlobInMemorySpaceUnderPressureRatio +
+ 1;
+ for (size_t i = 0; i < size_to_load; i++) {
+ BlobDataBuilder builder("fake");
+ builder.AppendData(kData, 1);
+ std::vector<scoped_refptr<ShareableBlobDataItem>> items =
+ CreateSharedDataItems(builder);
+ base::WeakPtr<QuotaAllocationTask> memory_task =
+ controller.ReserveMemoryQuota(items, GetMemoryRequestCallback());
+ EXPECT_FALSE(memory_task);
+ items[0]->set_state(ItemState::POPULATED_WITH_QUOTA);
+ small_items.insert(small_items.end(), items.begin(), items.end());
+ }
+ controller.NotifyMemoryItemsUsed(small_items);
+ EXPECT_FALSE(file_runner_->HasPendingTask());
+ EXPECT_EQ(size_to_load, controller.memory_usage());
+
+ controller.OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel::
+ MEMORY_PRESSURE_LEVEL_CRITICAL);
+
+ EXPECT_TRUE(file_runner_->HasPendingTask());
+ RunFileThreadTasks();
+
+ base::RunLoop().RunUntilIdle();
+
+ // 2 page files of size |kTestBlobStorageMaxBlobMemorySize *
+ // kTestMaxBlobInMemorySpaceUnderPressureRatio| should be evicted with 1 byte
+ // left in-memory.
+ EXPECT_EQ(1u, controller.memory_usage());
+ EXPECT_EQ(size_to_load - 1, controller.disk_usage());
+ return;
+}
+
} // namespace storage
diff --git a/chromium/storage/browser/blob/blob_reader.cc b/chromium/storage/browser/blob/blob_reader.cc
index 2d486872d63..56cf9ad5363 100644
--- a/chromium/storage/browser/blob/blob_reader.cc
+++ b/chromium/storage/browser/blob/blob_reader.cc
@@ -27,9 +27,12 @@
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/common/data_element.h"
+#include "storage/common/storage_histograms.h"
namespace storage {
namespace {
+const char kCacheStorageRecordBytesLabel[] = "DiskCache.CacheStorage";
+
bool IsFileType(DataElement::Type type) {
switch (type) {
case DataElement::TYPE_FILE:
@@ -145,6 +148,8 @@ void BlobReader::DidReadDiskCacheEntrySideData(const StatusCallback& done,
int result) {
if (result >= 0) {
DCHECK_EQ(expected_size, result);
+ if (result > 0)
+ storage::RecordBytesRead(kCacheStorageRecordBytesLabel, result);
done.Run(Status::DONE);
return;
}
@@ -584,6 +589,8 @@ BlobReader::Status BlobReader::ReadDiskCacheEntryItem(const BlobDataItem& item,
void BlobReader::DidReadDiskCacheEntry(int result) {
TRACE_EVENT_ASYNC_END1("Blob", "BlobRequest::ReadDiskCacheItem", this, "uuid",
blob_data_->uuid());
+ if (result > 0)
+ storage::RecordBytesRead(kCacheStorageRecordBytesLabel, result);
DidReadItem(result);
}
diff --git a/chromium/storage/browser/blob/blob_url_request_job_factory.cc b/chromium/storage/browser/blob/blob_url_request_job_factory.cc
index 1017386eb51..f710aba13a5 100644
--- a/chromium/storage/browser/blob/blob_url_request_job_factory.cc
+++ b/chromium/storage/browser/blob/blob_url_request_job_factory.cc
@@ -8,7 +8,9 @@
#include <utility>
#include "base/strings/string_util.h"
+#include "net/base/load_flags.h"
#include "net/base/request_priority.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_request_context.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
@@ -29,8 +31,35 @@ std::unique_ptr<net::URLRequest> BlobProtocolHandler::CreateBlobRequest(
const net::URLRequestContext* request_context,
net::URLRequest::Delegate* request_delegate) {
const GURL kBlobUrl("blob://see_user_data/");
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("blob_read", R"(
+ semantics {
+ sender: "BlobProtocolHandler"
+ description:
+ "Blobs are used for a variety of use cases, and are basically "
+ "immutable blocks of data. See https://chromium.googlesource.com/"
+ "chromium/src/+/master/storage/browser/blob/README.md for an "
+ "explanation of blobs and their implementation in Chrome. These "
+ "can be created by scripts in a website, web platform features, or "
+ "internally in the browser."
+ trigger:
+ "Request for reading the contents of a blob."
+ data:
+ "A reference to a Blob, File, or CacheStorage entry created from "
+ "script, a web platform feature, or browser internals."
+ destination: LOCAL
+ }
+ policy {
+ cookies_allowed: false
+ setting: "This feature cannot be disabled by settings."
+ policy_exception_justification:
+ "Not implemented. This is a local data fetch request and has no "
+ "network activity."
+ })");
std::unique_ptr<net::URLRequest> request = request_context->CreateRequest(
- kBlobUrl, net::DEFAULT_PRIORITY, request_delegate);
+ kBlobUrl, net::DEFAULT_PRIORITY, request_delegate, traffic_annotation);
+ request->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES);
SetRequestedBlobDataHandle(request.get(), std::move(blob_data_handle));
return request;
}
@@ -39,7 +68,7 @@ std::unique_ptr<net::URLRequest> BlobProtocolHandler::CreateBlobRequest(
void BlobProtocolHandler::SetRequestedBlobDataHandle(
net::URLRequest* request,
std::unique_ptr<BlobDataHandle> blob_data_handle) {
- request->SetUserData(&kUserDataKey, blob_data_handle.release());
+ request->SetUserData(&kUserDataKey, std::move(blob_data_handle));
}
// static
diff --git a/chromium/storage/browser/blob/blob_url_request_job_unittest.cc b/chromium/storage/browser/blob/blob_url_request_job_unittest.cc
index 10c31138c55..353eaf0827d 100644
--- a/chromium/storage/browser/blob/blob_url_request_job_unittest.cc
+++ b/chromium/storage/browser/blob/blob_url_request_job_unittest.cc
@@ -14,6 +14,7 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
@@ -25,6 +26,7 @@
#include "net/http/http_byte_range.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory_impl.h"
@@ -262,7 +264,8 @@ class BlobURLRequestJobTest : public testing::Test {
void TestRequest(const std::string& method,
const net::HttpRequestHeaders& extra_headers) {
request_ = url_request_context_.CreateRequest(
- GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_);
+ GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_,
+ TRAFFIC_ANNOTATION_FOR_TESTS);
request_->set_method(method);
if (!extra_headers.IsEmpty())
request_->SetExtraRequestHeaders(extra_headers);
diff --git a/chromium/storage/browser/blob/shareable_file_reference.cc b/chromium/storage/browser/blob/shareable_file_reference.cc
index b1ccc378c05..0f0e0b97632 100644
--- a/chromium/storage/browser/blob/shareable_file_reference.cc
+++ b/chromium/storage/browser/blob/shareable_file_reference.cc
@@ -86,12 +86,8 @@ scoped_refptr<ShareableFileReference> ShareableFileReference::GetOrCreate(
return scoped_refptr<ShareableFileReference>();
typedef std::pair<ShareableFileMap::iterator, bool> InsertResult;
- // Required for VS2010:
- // http://connect.microsoft.com/VisualStudio/feedback/
- // details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
- storage::ShareableFileReference* null_reference = NULL;
InsertResult result = g_file_map.Get().Insert(
- ShareableFileMap::value_type(scoped_file.path(), null_reference));
+ ShareableFileMap::value_type(scoped_file.path(), nullptr));
if (result.second == false) {
scoped_file.Release();
return scoped_refptr<ShareableFileReference>(result.first->second);
diff --git a/chromium/storage/browser/blob/view_blob_internals_job.cc b/chromium/storage/browser/blob/view_blob_internals_job.cc
index 9d4862adf1f..3f4c29b6628 100644
--- a/chromium/storage/browser/blob/view_blob_internals_job.cc
+++ b/chromium/storage/browser/blob/view_blob_internals_job.cc
@@ -180,34 +180,38 @@ int ViewBlobInternalsJob::GetData(
mime_type->assign("text/html");
charset->assign("UTF-8");
- data->clear();
- StartHTML(data);
- if (blob_storage_context_->registry().blob_map_.empty())
- data->append(kEmptyBlobStorageMessage);
- else
- GenerateHTML(data);
- EndHTML(data);
+ *data = GenerateHTML(blob_storage_context_);
return net::OK;
}
-void ViewBlobInternalsJob::GenerateHTML(std::string* out) const {
- for (auto iter = blob_storage_context_->registry().blob_map_.begin();
- iter != blob_storage_context_->registry().blob_map_.end(); ++iter) {
- AddHTMLBoldText(iter->first, out);
- GenerateHTMLForBlobData(*iter->second, iter->second->content_type(),
- iter->second->content_disposition(),
- iter->second->refcount(), out);
- }
- if (!blob_storage_context_->registry().url_to_uuid_.empty()) {
- AddHorizontalRule(out);
- for (auto iter = blob_storage_context_->registry().url_to_uuid_.begin();
- iter != blob_storage_context_->registry().url_to_uuid_.end(); ++iter) {
- AddHTMLBoldText(iter->first.spec(), out);
- StartHTMLList(out);
- AddHTMLListItem(kUUID, iter->second, out);
- EndHTMLList(out);
+std::string ViewBlobInternalsJob::GenerateHTML(
+ BlobStorageContext* blob_storage_context) {
+ std::string out;
+ StartHTML(&out);
+ if (blob_storage_context->registry().blob_map_.empty()) {
+ out.append(kEmptyBlobStorageMessage);
+ } else {
+ for (auto iter = blob_storage_context->registry().blob_map_.begin();
+ iter != blob_storage_context->registry().blob_map_.end(); ++iter) {
+ AddHTMLBoldText(iter->first, &out);
+ GenerateHTMLForBlobData(*iter->second, iter->second->content_type(),
+ iter->second->content_disposition(),
+ iter->second->refcount(), &out);
+ }
+ if (!blob_storage_context->registry().url_to_uuid_.empty()) {
+ AddHorizontalRule(&out);
+ for (auto iter = blob_storage_context->registry().url_to_uuid_.begin();
+ iter != blob_storage_context->registry().url_to_uuid_.end();
+ ++iter) {
+ AddHTMLBoldText(iter->first.spec(), &out);
+ StartHTMLList(&out);
+ AddHTMLListItem(kUUID, iter->second, &out);
+ EndHTMLList(&out);
+ }
}
}
+ EndHTML(&out);
+ return out;
}
void ViewBlobInternalsJob::GenerateHTMLForBlobData(
diff --git a/chromium/storage/browser/blob/view_blob_internals_job.h b/chromium/storage/browser/blob/view_blob_internals_job.h
index b1490fb86f6..ddb57725742 100644
--- a/chromium/storage/browser/blob/view_blob_internals_job.h
+++ b/chromium/storage/browser/blob/view_blob_internals_job.h
@@ -38,10 +38,11 @@ class STORAGE_EXPORT ViewBlobInternalsJob
bool IsRedirectResponse(GURL* location, int* http_status_code) override;
void Kill() override;
+ static std::string GenerateHTML(BlobStorageContext* blob_storage_context);
+
private:
~ViewBlobInternalsJob() override;
- void GenerateHTML(std::string* out) const;
static void GenerateHTMLForBlobData(const BlobEntry& blob_data,
const std::string& content_type,
const std::string& content_disposition,
diff --git a/chromium/storage/browser/database/database_quota_client.cc b/chromium/storage/browser/database/database_quota_client.cc
index f1b0c7c4cba..17946789737 100644
--- a/chromium/storage/browser/database/database_quota_client.cc
+++ b/chromium/storage/browser/database/database_quota_client.cc
@@ -103,7 +103,7 @@ DatabaseQuotaClient::DatabaseQuotaClient(
DatabaseQuotaClient::~DatabaseQuotaClient() {
if (db_tracker_thread_.get() &&
- !db_tracker_thread_->RunsTasksOnCurrentThread() && db_tracker_.get()) {
+ !db_tracker_thread_->RunsTasksInCurrentSequence() && db_tracker_.get()) {
DatabaseTracker* tracker = db_tracker_.get();
tracker->AddRef();
db_tracker_ = NULL;
diff --git a/chromium/storage/browser/database/database_tracker_unittest.cc b/chromium/storage/browser/database/database_tracker_unittest.cc
new file mode 100644
index 00000000000..53e53f3d7e7
--- /dev/null
+++ b/chromium/storage/browser/database/database_tracker_unittest.cc
@@ -0,0 +1,818 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "storage/browser/database/database_tracker.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/common/database/database_identifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/sqlite/sqlite3.h"
+
+using base::ASCIIToUTF16;
+using storage::DatabaseConnections;
+using storage::DatabaseTracker;
+using storage::OriginInfo;
+
+namespace {
+
+const char kOrigin1Url[] = "http://origin1";
+const char kOrigin2Url[] = "http://protected_origin2";
+
+class TestObserver : public storage::DatabaseTracker::Observer {
+ public:
+ TestObserver()
+ : new_notification_received_(false),
+ observe_size_changes_(true),
+ observe_scheduled_deletions_(true) {}
+ TestObserver(bool observe_size_changes, bool observe_scheduled_deletions)
+ : new_notification_received_(false),
+ observe_size_changes_(observe_size_changes),
+ observe_scheduled_deletions_(observe_scheduled_deletions) {}
+
+ ~TestObserver() override {}
+ void OnDatabaseSizeChanged(const std::string& origin_identifier,
+ const base::string16& database_name,
+ int64_t database_size) override {
+ if (!observe_size_changes_)
+ return;
+ new_notification_received_ = true;
+ origin_identifier_ = origin_identifier;
+ database_name_ = database_name;
+ database_size_ = database_size;
+ }
+ void OnDatabaseScheduledForDeletion(
+ const std::string& origin_identifier,
+ const base::string16& database_name) override {
+ if (!observe_scheduled_deletions_)
+ return;
+ new_notification_received_ = true;
+ origin_identifier_ = origin_identifier;
+ database_name_ = database_name;
+ }
+ bool DidReceiveNewNotification() {
+ bool temp_new_notification_received = new_notification_received_;
+ new_notification_received_ = false;
+ return temp_new_notification_received;
+ }
+ std::string GetNotificationOriginIdentifier() { return origin_identifier_; }
+ base::string16 GetNotificationDatabaseName() { return database_name_; }
+ int64_t GetNotificationDatabaseSize() { return database_size_; }
+
+ private:
+ bool new_notification_received_;
+ bool observe_size_changes_;
+ bool observe_scheduled_deletions_;
+ std::string origin_identifier_;
+ base::string16 database_name_;
+ int64_t database_size_;
+};
+
+void CheckNotificationReceived(TestObserver* observer,
+ const std::string& expected_origin_identifier,
+ const base::string16& expected_database_name,
+ int64_t expected_database_size) {
+ EXPECT_TRUE(observer->DidReceiveNewNotification());
+ EXPECT_EQ(expected_origin_identifier,
+ observer->GetNotificationOriginIdentifier());
+ EXPECT_EQ(expected_database_name, observer->GetNotificationDatabaseName());
+ EXPECT_EQ(expected_database_size, observer->GetNotificationDatabaseSize());
+}
+
+class TestQuotaManagerProxy : public storage::QuotaManagerProxy {
+ public:
+ TestQuotaManagerProxy()
+ : QuotaManagerProxy(NULL, NULL), registered_client_(NULL) {}
+
+ void RegisterClient(storage::QuotaClient* client) override {
+ EXPECT_FALSE(registered_client_);
+ registered_client_ = client;
+ }
+
+ void NotifyStorageAccessed(storage::QuotaClient::ID client_id,
+ const GURL& origin,
+ storage::StorageType type) override {
+ EXPECT_EQ(storage::QuotaClient::kDatabase, client_id);
+ EXPECT_EQ(storage::kStorageTypeTemporary, type);
+ accesses_[origin] += 1;
+ }
+
+ void NotifyStorageModified(storage::QuotaClient::ID client_id,
+ const GURL& origin,
+ storage::StorageType type,
+ int64_t delta) override {
+ EXPECT_EQ(storage::QuotaClient::kDatabase, client_id);
+ EXPECT_EQ(storage::kStorageTypeTemporary, type);
+ modifications_[origin].first += 1;
+ modifications_[origin].second += delta;
+ }
+
+ // Not needed for our tests.
+ void NotifyOriginInUse(const GURL& origin) override {}
+ void NotifyOriginNoLongerInUse(const GURL& origin) override {}
+ void SetUsageCacheEnabled(storage::QuotaClient::ID client_id,
+ const GURL& origin,
+ storage::StorageType type,
+ bool enabled) override {}
+ void GetUsageAndQuota(base::SequencedTaskRunner* original_task_runner,
+ const GURL& origin,
+ storage::StorageType type,
+ const UsageAndQuotaCallback& callback) override {}
+
+ void SimulateQuotaManagerDestroyed() {
+ if (registered_client_) {
+ registered_client_->OnQuotaManagerDestroyed();
+ registered_client_ = NULL;
+ }
+ }
+
+ bool WasAccessNotified(const GURL& origin) { return accesses_[origin] != 0; }
+
+ bool WasModificationNotified(const GURL& origin, int64_t amount) {
+ return modifications_[origin].first != 0 &&
+ modifications_[origin].second == amount;
+ }
+
+ void reset() {
+ accesses_.clear();
+ modifications_.clear();
+ }
+
+ storage::QuotaClient* registered_client_;
+
+ // Map from origin to count of access notifications.
+ std::map<GURL, int> accesses_;
+
+ // Map from origin to <count, sum of deltas>
+ std::map<GURL, std::pair<int, int64_t>> modifications_;
+
+ protected:
+ ~TestQuotaManagerProxy() override { EXPECT_FALSE(registered_client_); }
+};
+
+bool EnsureFileOfSize(const base::FilePath& file_path, int64_t length) {
+ base::File file(file_path,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
+ if (!file.IsValid())
+ return false;
+ return file.SetLength(length);
+}
+
+} // namespace
+
+namespace content {
+
+// We declare a helper class, and make it a friend of DatabaseTracker using
+// the FORWARD_DECLARE_TEST macro, and we implement all tests we want to run as
+// static methods of this class. Then we make our TEST() targets call these
+// static functions. This allows us to run each test in normal mode and
+// incognito mode without writing the same code twice.
+class DatabaseTracker_TestHelper_Test {
+ public:
+ static void TestDeleteOpenDatabase(bool incognito_mode) {
+ // Initialize the tracker database.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<MockSpecialStoragePolicy> special_storage_policy =
+ new MockSpecialStoragePolicy;
+ special_storage_policy->AddProtected(GURL(kOrigin2Url));
+ scoped_refptr<DatabaseTracker> tracker(
+ new DatabaseTracker(temp_dir.GetPath(), incognito_mode,
+ special_storage_policy.get(), NULL, NULL));
+
+ // Create and open three databases.
+ int64_t database_size = 0;
+ const std::string kOrigin1 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin1Url));
+ const std::string kOrigin2 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin2Url));
+ const base::string16 kDB1 = ASCIIToUTF16("db1");
+ const base::string16 kDB2 = ASCIIToUTF16("db2");
+ const base::string16 kDB3 = ASCIIToUTF16("db3");
+ const base::string16 kDescription = ASCIIToUTF16("database_description");
+
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, &database_size);
+ tracker->DatabaseOpened(kOrigin2, kDB3, kDescription, 0, &database_size);
+
+ EXPECT_TRUE(base::CreateDirectory(
+ tracker->DatabaseDirectory().Append(base::FilePath::FromUTF16Unsafe(
+ tracker->GetOriginDirectory(kOrigin1)))));
+ EXPECT_TRUE(base::CreateDirectory(
+ tracker->DatabaseDirectory().Append(base::FilePath::FromUTF16Unsafe(
+ tracker->GetOriginDirectory(kOrigin2)))));
+ EXPECT_EQ(
+ 1, base::WriteFile(tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
+ EXPECT_EQ(2, base::WriteFile(tracker->GetFullDBFilePath(kOrigin2, kDB2),
+ "aa", 2));
+ EXPECT_EQ(3, base::WriteFile(tracker->GetFullDBFilePath(kOrigin2, kDB3),
+ "aaa", 3));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ tracker->DatabaseModified(kOrigin2, kDB2);
+ tracker->DatabaseModified(kOrigin2, kDB3);
+
+ // Delete db1. Should also delete origin1.
+ TestObserver observer;
+ tracker->AddObserver(&observer);
+ net::TestCompletionCallback callback;
+ int result = tracker->DeleteDatabase(kOrigin1, kDB1, callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, result);
+ ASSERT_FALSE(callback.have_result());
+ EXPECT_TRUE(observer.DidReceiveNewNotification());
+ EXPECT_EQ(kOrigin1, observer.GetNotificationOriginIdentifier());
+ EXPECT_EQ(kDB1, observer.GetNotificationDatabaseName());
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+ result = callback.GetResult(result);
+ EXPECT_EQ(net::OK, result);
+ EXPECT_FALSE(
+ base::PathExists(tracker->DatabaseDirectory().AppendASCII(kOrigin1)));
+
+ // Recreate db1.
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_TRUE(base::CreateDirectory(
+ tracker->DatabaseDirectory().Append(base::FilePath::FromUTF16Unsafe(
+ tracker->GetOriginDirectory(kOrigin1)))));
+ EXPECT_EQ(
+ 1, base::WriteFile(tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+
+ // Setup file modification times. db1 and db2 are modified now, db3 three
+ // days ago.
+ base::Time now = base::Time::Now();
+ EXPECT_TRUE(
+ base::TouchFile(tracker->GetFullDBFilePath(kOrigin1, kDB1), now, now));
+ EXPECT_TRUE(
+ base::TouchFile(tracker->GetFullDBFilePath(kOrigin2, kDB2), now, now));
+ base::Time three_days_ago = now - base::TimeDelta::FromDays(3);
+ EXPECT_TRUE(base::TouchFile(tracker->GetFullDBFilePath(kOrigin2, kDB3),
+ three_days_ago, three_days_ago));
+
+ // Delete databases modified since yesterday. db2 is whitelisted.
+ base::Time yesterday = base::Time::Now();
+ yesterday -= base::TimeDelta::FromDays(1);
+ result = tracker->DeleteDataModifiedSince(yesterday, callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, result);
+ ASSERT_FALSE(callback.have_result());
+ EXPECT_TRUE(observer.DidReceiveNewNotification());
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+ tracker->DatabaseClosed(kOrigin2, kDB2);
+ result = callback.GetResult(result);
+ EXPECT_EQ(net::OK, result);
+ EXPECT_FALSE(
+ base::PathExists(tracker->DatabaseDirectory().AppendASCII(kOrigin1)));
+ EXPECT_TRUE(base::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2)));
+ EXPECT_TRUE(base::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB3)));
+
+ tracker->DatabaseClosed(kOrigin2, kDB3);
+ tracker->RemoveObserver(&observer);
+ }
+
+ static void TestDatabaseTracker(bool incognito_mode) {
+ // Initialize the tracker database.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<MockSpecialStoragePolicy> special_storage_policy =
+ new MockSpecialStoragePolicy;
+ special_storage_policy->AddProtected(GURL(kOrigin2Url));
+ scoped_refptr<DatabaseTracker> tracker(
+ new DatabaseTracker(temp_dir.GetPath(), incognito_mode,
+ special_storage_policy.get(), NULL, NULL));
+
+ // Add two observers.
+ TestObserver observer1;
+ TestObserver observer2;
+ tracker->AddObserver(&observer1);
+ tracker->AddObserver(&observer2);
+
+ // Open three new databases.
+ int64_t database_size = 0;
+ const std::string kOrigin1 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin1Url));
+ const std::string kOrigin2 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin2Url));
+ const base::string16 kDB1 = ASCIIToUTF16("db1");
+ const base::string16 kDB2 = ASCIIToUTF16("db2");
+ const base::string16 kDB3 = ASCIIToUTF16("db3");
+ const base::string16 kDescription = ASCIIToUTF16("database_description");
+
+ // Get the info for kOrigin1 and kOrigin2
+ DatabaseTracker::CachedOriginInfo* origin1_info =
+ tracker->GetCachedOriginInfo(kOrigin1);
+ DatabaseTracker::CachedOriginInfo* origin2_info =
+ tracker->GetCachedOriginInfo(kOrigin1);
+ EXPECT_TRUE(origin1_info);
+ EXPECT_TRUE(origin2_info);
+
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+ tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+ tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+
+ // Write some data to each file and check that the listeners are
+ // called with the appropriate values.
+ EXPECT_TRUE(base::CreateDirectory(
+ tracker->DatabaseDirectory().Append(base::FilePath::FromUTF16Unsafe(
+ tracker->GetOriginDirectory(kOrigin1)))));
+ EXPECT_TRUE(base::CreateDirectory(
+ tracker->DatabaseDirectory().Append(base::FilePath::FromUTF16Unsafe(
+ tracker->GetOriginDirectory(kOrigin2)))));
+ EXPECT_EQ(
+ 1, base::WriteFile(tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
+ EXPECT_EQ(2, base::WriteFile(tracker->GetFullDBFilePath(kOrigin2, kDB2),
+ "aa", 2));
+ EXPECT_EQ(4, base::WriteFile(tracker->GetFullDBFilePath(kOrigin1, kDB3),
+ "aaaa", 4));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1);
+ tracker->DatabaseModified(kOrigin2, kDB2);
+ CheckNotificationReceived(&observer1, kOrigin2, kDB2, 2);
+ CheckNotificationReceived(&observer2, kOrigin2, kDB2, 2);
+ tracker->DatabaseModified(kOrigin1, kDB3);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB3, 4);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB3, 4);
+
+ // Close all databases
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+ tracker->DatabaseClosed(kOrigin2, kDB2);
+ tracker->DatabaseClosed(kOrigin1, kDB3);
+
+ // Open an existing database and check the reported size
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_EQ(1, database_size);
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+
+ // Remove an observer; this should clear all caches.
+ tracker->RemoveObserver(&observer2);
+
+ // Close the tracker database and clear all caches.
+ // Then make sure that DatabaseOpened() still returns the correct result.
+ tracker->CloseTrackerDatabaseAndClearCaches();
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_EQ(1, database_size);
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+
+ // Remove all observers.
+ tracker->RemoveObserver(&observer1);
+
+ // Trying to delete a database in use should fail
+ tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0, &database_size);
+ EXPECT_FALSE(tracker->DeleteClosedDatabase(kOrigin1, kDB3));
+ origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
+ EXPECT_TRUE(origin1_info);
+ EXPECT_EQ(4, origin1_info->GetDatabaseSize(kDB3));
+ tracker->DatabaseClosed(kOrigin1, kDB3);
+
+ // Delete a database and make sure the space used by that origin is updated
+ EXPECT_TRUE(tracker->DeleteClosedDatabase(kOrigin1, kDB3));
+ origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
+ EXPECT_TRUE(origin1_info);
+ EXPECT_EQ(1, origin1_info->GetDatabaseSize(kDB1));
+ EXPECT_EQ(0, origin1_info->GetDatabaseSize(kDB3));
+
+ // Get all data for all origins
+ std::vector<OriginInfo> origins_info;
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
+ EXPECT_EQ(size_t(2), origins_info.size());
+ EXPECT_EQ(kOrigin1, origins_info[0].GetOriginIdentifier());
+ EXPECT_EQ(1, origins_info[0].TotalSize());
+ EXPECT_EQ(1, origins_info[0].GetDatabaseSize(kDB1));
+ EXPECT_EQ(0, origins_info[0].GetDatabaseSize(kDB3));
+
+ EXPECT_EQ(kOrigin2, origins_info[1].GetOriginIdentifier());
+ EXPECT_EQ(2, origins_info[1].TotalSize());
+
+ // Trying to delete an origin with databases in use should fail
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_FALSE(tracker->DeleteOrigin(kOrigin1, false));
+ origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
+ EXPECT_TRUE(origin1_info);
+ EXPECT_EQ(1, origin1_info->GetDatabaseSize(kDB1));
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+
+ // Delete an origin that doesn't have any database in use
+ EXPECT_TRUE(tracker->DeleteOrigin(kOrigin1, false));
+ origins_info.clear();
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
+ EXPECT_EQ(size_t(1), origins_info.size());
+ EXPECT_EQ(kOrigin2, origins_info[0].GetOriginIdentifier());
+
+ origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
+ EXPECT_TRUE(origin1_info);
+ EXPECT_EQ(0, origin1_info->TotalSize());
+ }
+
+ static void DatabaseTrackerQuotaIntegration() {
+ const GURL kOrigin(kOrigin1Url);
+ const std::string kOriginId = storage::GetIdentifierFromOrigin(kOrigin);
+ const base::string16 kName = ASCIIToUTF16("name");
+ const base::string16 kDescription = ASCIIToUTF16("description");
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Initialize the tracker with a QuotaManagerProxy
+ scoped_refptr<TestQuotaManagerProxy> test_quota_proxy(
+ new TestQuotaManagerProxy);
+ scoped_refptr<DatabaseTracker> tracker(
+ new DatabaseTracker(temp_dir.GetPath(), false /* incognito */, NULL,
+ test_quota_proxy.get(), NULL));
+ EXPECT_TRUE(test_quota_proxy->registered_client_);
+
+ // Create a database and modify it a couple of times, close it,
+ // then delete it. Observe the tracker notifies accordingly.
+
+ int64_t database_size = 0;
+ tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, &database_size);
+ EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin));
+ test_quota_proxy->reset();
+
+ base::FilePath db_file(tracker->GetFullDBFilePath(kOriginId, kName));
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 10));
+ tracker->DatabaseModified(kOriginId, kName);
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 10));
+ test_quota_proxy->reset();
+
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 100));
+ tracker->DatabaseModified(kOriginId, kName);
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 90));
+ test_quota_proxy->reset();
+
+ tracker->DatabaseClosed(kOriginId, kName);
+ EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin));
+ EXPECT_EQ(net::OK, tracker->DeleteDatabase(kOriginId, kName,
+ net::CompletionCallback()));
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, -100));
+ test_quota_proxy->reset();
+
+ // Create a database and modify it, try to delete it while open,
+ // then close it (at which time deletion will actually occur).
+ // Observe the tracker notifies accordingly.
+
+ tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, &database_size);
+ EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin));
+ test_quota_proxy->reset();
+
+ db_file = tracker->GetFullDBFilePath(kOriginId, kName);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 100));
+ tracker->DatabaseModified(kOriginId, kName);
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 100));
+ test_quota_proxy->reset();
+
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ tracker->DeleteDatabase(kOriginId, kName, net::CompletionCallback()));
+ EXPECT_FALSE(test_quota_proxy->WasModificationNotified(kOrigin, -100));
+
+ tracker->DatabaseClosed(kOriginId, kName);
+ EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin));
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, -100));
+ test_quota_proxy->reset();
+
+ // Create a database and up the file size without telling
+ // the tracker about the modification, than simulate a
+ // a renderer crash.
+ // Observe the tracker notifies accordingly.
+
+ tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, &database_size);
+ EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin));
+ test_quota_proxy->reset();
+ db_file = tracker->GetFullDBFilePath(kOriginId, kName);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 100));
+ DatabaseConnections crashed_renderer_connections;
+ crashed_renderer_connections.AddConnection(kOriginId, kName);
+ EXPECT_FALSE(test_quota_proxy->WasModificationNotified(kOrigin, 100));
+ tracker->CloseDatabases(crashed_renderer_connections);
+ EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 100));
+
+ // Cleanup.
+ crashed_renderer_connections.RemoveAllConnections();
+ test_quota_proxy->SimulateQuotaManagerDestroyed();
+ }
+
+ static void DatabaseTrackerClearSessionOnlyDatabasesOnExit() {
+ int64_t database_size = 0;
+ const std::string kOrigin1 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin1Url));
+ const std::string kOrigin2 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin2Url));
+ const base::string16 kDB1 = ASCIIToUTF16("db1");
+ const base::string16 kDB2 = ASCIIToUTF16("db2");
+ const base::string16 kDescription = ASCIIToUTF16("database_description");
+
+ // Initialize the tracker database.
+ base::MessageLoop message_loop;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath origin1_db_dir;
+ base::FilePath origin2_db_dir;
+ {
+ scoped_refptr<MockSpecialStoragePolicy> special_storage_policy =
+ new MockSpecialStoragePolicy;
+ special_storage_policy->AddSessionOnly(GURL(kOrigin2Url));
+ scoped_refptr<DatabaseTracker> tracker(new DatabaseTracker(
+ temp_dir.GetPath(), false, special_storage_policy.get(), NULL,
+ base::ThreadTaskRunnerHandle::Get().get()));
+
+ // Open two new databases.
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+ tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+
+ // Write some data to each file.
+ base::FilePath db_file;
+ db_file = tracker->GetFullDBFilePath(kOrigin1, kDB1);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 1));
+
+ db_file = tracker->GetFullDBFilePath(kOrigin2, kDB2);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 2));
+
+ // Store the origin database directories as long as they still exist.
+ origin1_db_dir = tracker->GetFullDBFilePath(kOrigin1, kDB1).DirName();
+ origin2_db_dir = tracker->GetFullDBFilePath(kOrigin2, kDB2).DirName();
+
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ tracker->DatabaseModified(kOrigin2, kDB2);
+
+ // Close all databases.
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+ tracker->DatabaseClosed(kOrigin2, kDB2);
+
+ tracker->Shutdown();
+ }
+
+ // At this point, the database tracker should be gone. Create a new one.
+ scoped_refptr<DatabaseTracker> tracker(
+ new DatabaseTracker(temp_dir.GetPath(), false, NULL, NULL, NULL));
+
+ // Get all data for all origins.
+ std::vector<OriginInfo> origins_info;
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
+ // kOrigin1 was not session-only, so it survived. kOrigin2 was session-only
+ // and it got deleted.
+ EXPECT_EQ(size_t(1), origins_info.size());
+ EXPECT_EQ(kOrigin1, origins_info[0].GetOriginIdentifier());
+ EXPECT_TRUE(base::PathExists(tracker->GetFullDBFilePath(kOrigin1, kDB1)));
+ EXPECT_EQ(base::FilePath(), tracker->GetFullDBFilePath(kOrigin2, kDB2));
+
+ // The origin directory of kOrigin1 remains, but the origin directory of
+ // kOrigin2 is deleted.
+ EXPECT_TRUE(base::PathExists(origin1_db_dir));
+ EXPECT_FALSE(base::PathExists(origin2_db_dir));
+ }
+
+ static void DatabaseTrackerSetForceKeepSessionState() {
+ int64_t database_size = 0;
+ const std::string kOrigin1 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin1Url));
+ const std::string kOrigin2 =
+ storage::GetIdentifierFromOrigin(GURL(kOrigin2Url));
+ const base::string16 kDB1 = ASCIIToUTF16("db1");
+ const base::string16 kDB2 = ASCIIToUTF16("db2");
+ const base::string16 kDescription = ASCIIToUTF16("database_description");
+
+ // Initialize the tracker database.
+ base::MessageLoop message_loop;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath origin1_db_dir;
+ base::FilePath origin2_db_dir;
+ {
+ scoped_refptr<MockSpecialStoragePolicy> special_storage_policy =
+ new MockSpecialStoragePolicy;
+ special_storage_policy->AddSessionOnly(GURL(kOrigin2Url));
+ scoped_refptr<DatabaseTracker> tracker(new DatabaseTracker(
+ temp_dir.GetPath(), false, special_storage_policy.get(), NULL,
+ base::ThreadTaskRunnerHandle::Get().get()));
+ tracker->SetForceKeepSessionState();
+
+ // Open two new databases.
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+ tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, &database_size);
+ EXPECT_EQ(0, database_size);
+
+ // Write some data to each file.
+ base::FilePath db_file;
+ db_file = tracker->GetFullDBFilePath(kOrigin1, kDB1);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 1));
+
+ db_file = tracker->GetFullDBFilePath(kOrigin2, kDB2);
+ EXPECT_TRUE(base::CreateDirectory(db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(db_file, 2));
+
+ // Store the origin database directories as long as they still exist.
+ origin1_db_dir = tracker->GetFullDBFilePath(kOrigin1, kDB1).DirName();
+ origin2_db_dir = tracker->GetFullDBFilePath(kOrigin2, kDB2).DirName();
+
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ tracker->DatabaseModified(kOrigin2, kDB2);
+
+ // Close all databases.
+ tracker->DatabaseClosed(kOrigin1, kDB1);
+ tracker->DatabaseClosed(kOrigin2, kDB2);
+
+ tracker->Shutdown();
+ }
+
+ // At this point, the database tracker should be gone. Create a new one.
+ scoped_refptr<DatabaseTracker> tracker(
+ new DatabaseTracker(temp_dir.GetPath(), false, NULL, NULL, NULL));
+
+ // Get all data for all origins.
+ std::vector<OriginInfo> origins_info;
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
+ // No origins were deleted.
+ EXPECT_EQ(size_t(2), origins_info.size());
+ EXPECT_TRUE(base::PathExists(tracker->GetFullDBFilePath(kOrigin1, kDB1)));
+ EXPECT_TRUE(base::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2)));
+
+ EXPECT_TRUE(base::PathExists(origin1_db_dir));
+ EXPECT_TRUE(base::PathExists(origin2_db_dir));
+ }
+
+ static void EmptyDatabaseNameIsValid() {
+ const GURL kOrigin(kOrigin1Url);
+ const std::string kOriginId = storage::GetIdentifierFromOrigin(kOrigin);
+ const base::string16 kEmptyName;
+ const base::string16 kDescription(ASCIIToUTF16("description"));
+ const base::string16 kChangedDescription(
+ ASCIIToUTF16("changed_description"));
+
+ // Initialize a tracker database, no need to put it on disk.
+ const bool kUseInMemoryTrackerDatabase = true;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DatabaseTracker> tracker(new DatabaseTracker(
+ temp_dir.GetPath(), kUseInMemoryTrackerDatabase, NULL, NULL, NULL));
+
+ // Starts off with no databases.
+ std::vector<OriginInfo> infos;
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos));
+ EXPECT_TRUE(infos.empty());
+
+ // Create a db with an empty name.
+ int64_t database_size = -1;
+ tracker->DatabaseOpened(kOriginId, kEmptyName, kDescription, 0,
+ &database_size);
+ EXPECT_EQ(0, database_size);
+ tracker->DatabaseModified(kOriginId, kEmptyName);
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos));
+ EXPECT_EQ(1u, infos.size());
+ EXPECT_EQ(kDescription, infos[0].GetDatabaseDescription(kEmptyName));
+ EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kEmptyName).empty());
+ tracker->DatabaseOpened(kOriginId, kEmptyName, kChangedDescription, 0,
+ &database_size);
+ infos.clear();
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos));
+ EXPECT_EQ(1u, infos.size());
+ EXPECT_EQ(kChangedDescription, infos[0].GetDatabaseDescription(kEmptyName));
+ tracker->DatabaseClosed(kOriginId, kEmptyName);
+ tracker->DatabaseClosed(kOriginId, kEmptyName);
+
+ // Deleting it should return to the initial state.
+ EXPECT_EQ(net::OK, tracker->DeleteDatabase(kOriginId, kEmptyName,
+ net::CompletionCallback()));
+ infos.clear();
+ EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos));
+ EXPECT_TRUE(infos.empty());
+ }
+
+ static void HandleSqliteError() {
+ const GURL kOrigin(kOrigin1Url);
+ const std::string kOriginId = storage::GetIdentifierFromOrigin(kOrigin);
+ const base::string16 kName(ASCIIToUTF16("name"));
+ const base::string16 kDescription(ASCIIToUTF16("description"));
+
+ // Initialize a tracker database, no need to put it on disk.
+ const bool kUseInMemoryTrackerDatabase = true;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DatabaseTracker> tracker(new DatabaseTracker(
+ temp_dir.GetPath(), kUseInMemoryTrackerDatabase, NULL, NULL, NULL));
+
+ // Setup to observe OnScheduledForDelete notifications.
+ TestObserver observer(false, true);
+ tracker->AddObserver(&observer);
+
+ // Verify does no harm when there is no such database.
+ tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT);
+ EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName));
+ EXPECT_FALSE(observer.DidReceiveNewNotification());
+
+ // --------------------------------------------------------
+ // Create a record of a database in the tracker db and create
+ // a spoof_db_file on disk in the expected location.
+ int64_t database_size = 0;
+ tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, &database_size);
+ base::FilePath spoof_db_file = tracker->GetFullDBFilePath(kOriginId, kName);
+ EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kName).empty());
+ EXPECT_TRUE(base::CreateDirectory(spoof_db_file.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(spoof_db_file, 1));
+
+ // Verify does no harm with a non-error is reported.
+ tracker->HandleSqliteError(kOriginId, kName, SQLITE_OK);
+ EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName));
+ EXPECT_FALSE(observer.DidReceiveNewNotification());
+
+ // Verify that with a connection open, the db is scheduled for deletion,
+ // but that the file still exists.
+ tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT);
+ EXPECT_TRUE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName));
+ EXPECT_TRUE(observer.DidReceiveNewNotification());
+ EXPECT_TRUE(base::PathExists(spoof_db_file));
+
+ // Verify that once closed, the file is deleted and the record in the
+ // tracker db is removed.
+ tracker->DatabaseClosed(kOriginId, kName);
+ EXPECT_FALSE(base::PathExists(spoof_db_file));
+ EXPECT_TRUE(tracker->GetFullDBFilePath(kOriginId, kName).empty());
+
+ // --------------------------------------------------------
+ // Create another record of a database in the tracker db and create
+ // a spoof_db_file on disk in the expected location.
+ tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, &database_size);
+ base::FilePath spoof_db_file2 =
+ tracker->GetFullDBFilePath(kOriginId, kName);
+ EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kName).empty());
+ EXPECT_NE(spoof_db_file, spoof_db_file2);
+ EXPECT_TRUE(base::CreateDirectory(spoof_db_file2.DirName()));
+ EXPECT_TRUE(EnsureFileOfSize(spoof_db_file2, 1));
+
+ // Verify that with no connection open, the db is deleted immediately.
+ tracker->DatabaseClosed(kOriginId, kName);
+ tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT);
+ EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName));
+ EXPECT_FALSE(observer.DidReceiveNewNotification());
+ EXPECT_TRUE(tracker->GetFullDBFilePath(kOriginId, kName).empty());
+ EXPECT_FALSE(base::PathExists(spoof_db_file2));
+
+ tracker->RemoveObserver(&observer);
+ }
+};
+
+TEST(DatabaseTrackerTest, DeleteOpenDatabase) {
+ DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(false);
+}
+
+TEST(DatabaseTrackerTest, DeleteOpenDatabaseIncognitoMode) {
+ DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(true);
+}
+
+TEST(DatabaseTrackerTest, DatabaseTracker) {
+ DatabaseTracker_TestHelper_Test::TestDatabaseTracker(false);
+}
+
+TEST(DatabaseTrackerTest, DatabaseTrackerIncognitoMode) {
+ DatabaseTracker_TestHelper_Test::TestDatabaseTracker(true);
+}
+
+TEST(DatabaseTrackerTest, DatabaseTrackerQuotaIntegration) {
+ // There is no difference in behavior between incognito and not.
+ DatabaseTracker_TestHelper_Test::DatabaseTrackerQuotaIntegration();
+}
+
+TEST(DatabaseTrackerTest, DatabaseTrackerClearSessionOnlyDatabasesOnExit) {
+ // Only works for regular mode.
+ DatabaseTracker_TestHelper_Test::
+ DatabaseTrackerClearSessionOnlyDatabasesOnExit();
+}
+
+TEST(DatabaseTrackerTest, DatabaseTrackerSetForceKeepSessionState) {
+ // Only works for regular mode.
+ DatabaseTracker_TestHelper_Test::DatabaseTrackerSetForceKeepSessionState();
+}
+
+TEST(DatabaseTrackerTest, EmptyDatabaseNameIsValid) {
+ DatabaseTracker_TestHelper_Test::EmptyDatabaseNameIsValid();
+}
+
+TEST(DatabaseTrackerTest, HandleSqliteError) {
+ DatabaseTracker_TestHelper_Test::HandleSqliteError();
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/copy_or_move_file_validator_unittest.cc b/chromium/storage/browser/fileapi/copy_or_move_file_validator_unittest.cc
new file mode 100644
index 00000000000..c1388386575
--- /dev/null
+++ b/chromium/storage/browser/fileapi/copy_or_move_file_validator_unittest.cc
@@ -0,0 +1,337 @@
+// Copyright 2013 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 <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/blob/shareable_file_reference.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/test_file_system_backend.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::CopyOrMoveFileValidator;
+using storage::CopyOrMoveFileValidatorFactory;
+using storage::FileSystemURL;
+
+namespace content {
+
+namespace {
+
+const storage::FileSystemType kNoValidatorType =
+ storage::kFileSystemTypeTemporary;
+const storage::FileSystemType kWithValidatorType = storage::kFileSystemTypeTest;
+
+void ExpectOk(const GURL& origin_url,
+ const std::string& name,
+ base::File::Error error) {
+ ASSERT_EQ(base::File::FILE_OK, error);
+}
+
+class CopyOrMoveFileValidatorTestHelper {
+ public:
+ CopyOrMoveFileValidatorTestHelper(const GURL& origin,
+ storage::FileSystemType src_type,
+ storage::FileSystemType dest_type)
+ : origin_(origin), src_type_(src_type), dest_type_(dest_type) {}
+
+ ~CopyOrMoveFileValidatorTestHelper() {
+ file_system_context_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void SetUp() {
+ ASSERT_TRUE(base_.CreateUniqueTempDir());
+ base::FilePath base_dir = base_.GetPath();
+
+ file_system_context_ = CreateFileSystemContextForTesting(NULL, base_dir);
+
+ // Set up TestFileSystemBackend to require CopyOrMoveFileValidator.
+ storage::FileSystemBackend* test_file_system_backend =
+ file_system_context_->GetFileSystemBackend(kWithValidatorType);
+ static_cast<TestFileSystemBackend*>(test_file_system_backend)->
+ set_require_copy_or_move_validator(true);
+
+ // Sets up source.
+ storage::FileSystemBackend* src_file_system_backend =
+ file_system_context_->GetFileSystemBackend(src_type_);
+ src_file_system_backend->ResolveURL(
+ FileSystemURL::CreateForTest(origin_, src_type_, base::FilePath()),
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, CreateDirectory(SourceURL("")));
+
+ // Sets up dest.
+ DCHECK_EQ(kWithValidatorType, dest_type_);
+ ASSERT_EQ(base::File::FILE_OK, CreateDirectory(DestURL("")));
+
+ copy_src_ = SourceURL("copy_src.jpg");
+ move_src_ = SourceURL("move_src.jpg");
+ copy_dest_ = DestURL("copy_dest.jpg");
+ move_dest_ = DestURL("move_dest.jpg");
+
+ ASSERT_EQ(base::File::FILE_OK, CreateFile(copy_src_, 10));
+ ASSERT_EQ(base::File::FILE_OK, CreateFile(move_src_, 10));
+
+ ASSERT_TRUE(FileExists(copy_src_, 10));
+ ASSERT_TRUE(FileExists(move_src_, 10));
+ ASSERT_FALSE(FileExists(copy_dest_, 10));
+ ASSERT_FALSE(FileExists(move_dest_, 10));
+ }
+
+ void SetMediaCopyOrMoveFileValidatorFactory(
+ std::unique_ptr<storage::CopyOrMoveFileValidatorFactory> factory) {
+ TestFileSystemBackend* backend = static_cast<TestFileSystemBackend*>(
+ file_system_context_->GetFileSystemBackend(kWithValidatorType));
+ backend->InitializeCopyOrMoveFileValidatorFactory(std::move(factory));
+ }
+
+ void CopyTest(base::File::Error expected) {
+ ASSERT_TRUE(FileExists(copy_src_, 10));
+ ASSERT_FALSE(FileExists(copy_dest_, 10));
+
+ EXPECT_EQ(expected,
+ AsyncFileTestHelper::Copy(
+ file_system_context_.get(), copy_src_, copy_dest_));
+
+ EXPECT_TRUE(FileExists(copy_src_, 10));
+ if (expected == base::File::FILE_OK)
+ EXPECT_TRUE(FileExists(copy_dest_, 10));
+ else
+ EXPECT_FALSE(FileExists(copy_dest_, 10));
+ };
+
+ void MoveTest(base::File::Error expected) {
+ ASSERT_TRUE(FileExists(move_src_, 10));
+ ASSERT_FALSE(FileExists(move_dest_, 10));
+
+ EXPECT_EQ(expected,
+ AsyncFileTestHelper::Move(
+ file_system_context_.get(), move_src_, move_dest_));
+
+ if (expected == base::File::FILE_OK) {
+ EXPECT_FALSE(FileExists(move_src_, 10));
+ EXPECT_TRUE(FileExists(move_dest_, 10));
+ } else {
+ EXPECT_TRUE(FileExists(move_src_, 10));
+ EXPECT_FALSE(FileExists(move_dest_, 10));
+ }
+ };
+
+ private:
+ FileSystemURL SourceURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, src_type_,
+ base::FilePath().AppendASCII("src").AppendASCII(path));
+ }
+
+ FileSystemURL DestURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, dest_type_,
+ base::FilePath().AppendASCII("dest").AppendASCII(path));
+ }
+
+ base::File::Error CreateFile(const FileSystemURL& url, size_t size) {
+ base::File::Error result =
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
+ if (result != base::File::FILE_OK)
+ return result;
+ return AsyncFileTestHelper::TruncateFile(
+ file_system_context_.get(), url, size);
+ }
+
+ base::File::Error CreateDirectory(const FileSystemURL& url) {
+ return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
+ url);
+ }
+
+ bool FileExists(const FileSystemURL& url, int64_t expected_size) {
+ return AsyncFileTestHelper::FileExists(
+ file_system_context_.get(), url, expected_size);
+ }
+
+ base::ScopedTempDir base_;
+
+ const GURL origin_;
+
+ const storage::FileSystemType src_type_;
+ const storage::FileSystemType dest_type_;
+ std::string src_fsid_;
+ std::string dest_fsid_;
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+
+ FileSystemURL copy_src_;
+ FileSystemURL copy_dest_;
+ FileSystemURL move_src_;
+ FileSystemURL move_dest_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveFileValidatorTestHelper);
+};
+
+// For TestCopyOrMoveFileValidatorFactory
+enum Validity {
+ VALID,
+ PRE_WRITE_INVALID,
+ POST_WRITE_INVALID
+};
+
+class TestCopyOrMoveFileValidatorFactory
+ : public storage::CopyOrMoveFileValidatorFactory {
+ public:
+ // A factory that creates validators that accept everything or nothing.
+ // TODO(gbillock): switch args to enum or something
+ explicit TestCopyOrMoveFileValidatorFactory(Validity validity)
+ : validity_(validity) {}
+ ~TestCopyOrMoveFileValidatorFactory() override {}
+
+ storage::CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
+ const FileSystemURL& /*src_url*/,
+ const base::FilePath& /*platform_path*/) override {
+ return new TestCopyOrMoveFileValidator(validity_);
+ }
+
+ private:
+ class TestCopyOrMoveFileValidator : public CopyOrMoveFileValidator {
+ public:
+ explicit TestCopyOrMoveFileValidator(Validity validity)
+ : result_(validity == VALID || validity == POST_WRITE_INVALID ?
+ base::File::FILE_OK :
+ base::File::FILE_ERROR_SECURITY),
+ write_result_(validity == VALID || validity == PRE_WRITE_INVALID ?
+ base::File::FILE_OK :
+ base::File::FILE_ERROR_SECURITY) {
+ }
+ ~TestCopyOrMoveFileValidator() override {}
+
+ void StartPreWriteValidation(
+ const ResultCallback& result_callback) override {
+ // Post the result since a real validator must do work asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result_));
+ }
+
+ void StartPostWriteValidation(
+ const base::FilePath& dest_platform_path,
+ const ResultCallback& result_callback) override {
+ // Post the result since a real validator must do work asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(result_callback, write_result_));
+ }
+
+ private:
+ base::File::Error result_;
+ base::File::Error write_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidator);
+ };
+
+ Validity validity_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidatorFactory);
+};
+
+} // namespace
+
+TEST(CopyOrMoveFileValidatorTest, NoValidatorWithinSameFSType) {
+ // Within a file system type, validation is not expected, so it should
+ // work for kWithValidatorType without a validator set.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kWithValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ helper.CopyTest(base::File::FILE_OK);
+ helper.MoveTest(base::File::FILE_OK);
+}
+
+TEST(CopyOrMoveFileValidatorTest, MissingValidator) {
+ // Copying or moving into a kWithValidatorType requires a file
+ // validator. An error is expected if copy is attempted without a validator.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ helper.CopyTest(base::File::FILE_ERROR_SECURITY);
+ helper.MoveTest(base::File::FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, AcceptAll) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(VALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));
+
+ helper.CopyTest(base::File::FILE_OK);
+ helper.MoveTest(base::File::FILE_OK);
+}
+
+TEST(CopyOrMoveFileValidatorTest, AcceptNone) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));
+
+ helper.CopyTest(base::File::FILE_ERROR_SECURITY);
+ helper.MoveTest(base::File::FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, OverrideValidator) {
+ // Once set, you can not override the validator.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ std::unique_ptr<CopyOrMoveFileValidatorFactory> reject_factory(
+ new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(reject_factory));
+
+ std::unique_ptr<CopyOrMoveFileValidatorFactory> accept_factory(
+ new TestCopyOrMoveFileValidatorFactory(VALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(accept_factory));
+
+ helper.CopyTest(base::File::FILE_ERROR_SECURITY);
+ helper.MoveTest(base::File::FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, RejectPostWrite) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(POST_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));
+
+ helper.CopyTest(base::File::FILE_ERROR_SECURITY);
+ helper.MoveTest(base::File::FILE_ERROR_SECURITY);
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/chromium/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
new file mode 100644
index 00000000000..6e52575e62e
--- /dev/null
+++ b/chromium/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
@@ -0,0 +1,878 @@
+// Copyright 2013 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 <stddef.h>
+#include <stdint.h>
+#include <map>
+#include <queue>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/copy_or_move_operation_delegate.h"
+#include "storage/browser/fileapi/file_stream_reader.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/fileapi_test_file_set.h"
+#include "storage/browser/test/mock_quota_manager.h"
+#include "storage/browser/test/mock_quota_manager_proxy.h"
+#include "storage/browser/test/test_file_system_backend.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_mount_option.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::CopyOrMoveOperationDelegate;
+using storage::FileStreamWriter;
+using storage::FileSystemOperation;
+using storage::FileSystemURL;
+
+namespace content {
+
+typedef storage::FileSystemOperation::FileEntryList FileEntryList;
+
+namespace {
+
+void ExpectOk(const GURL& origin_url,
+ const std::string& name,
+ base::File::Error error) {
+ ASSERT_EQ(base::File::FILE_OK, error);
+}
+
+class TestValidatorFactory : public storage::CopyOrMoveFileValidatorFactory {
+ public:
+ // A factory that creates validators that accept everything or nothing.
+ TestValidatorFactory() {}
+ ~TestValidatorFactory() override {}
+
+ storage::CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
+ const FileSystemURL& /*src_url*/,
+ const base::FilePath& /*platform_path*/) override {
+ // Move arg management to TestValidator?
+ return new TestValidator(true, true, std::string("2"));
+ }
+
+ private:
+ class TestValidator : public storage::CopyOrMoveFileValidator {
+ public:
+ explicit TestValidator(bool pre_copy_valid,
+ bool post_copy_valid,
+ const std::string& reject_string)
+ : result_(pre_copy_valid ? base::File::FILE_OK :
+ base::File::FILE_ERROR_SECURITY),
+ write_result_(post_copy_valid ? base::File::FILE_OK :
+ base::File::FILE_ERROR_SECURITY),
+ reject_string_(reject_string) {
+ }
+ ~TestValidator() override {}
+
+ void StartPreWriteValidation(
+ const ResultCallback& result_callback) override {
+ // Post the result since a real validator must do work asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result_));
+ }
+
+ void StartPostWriteValidation(
+ const base::FilePath& dest_platform_path,
+ const ResultCallback& result_callback) override {
+ base::File::Error result = write_result_;
+ std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe();
+ if (unsafe.find(reject_string_) != std::string::npos) {
+ result = base::File::FILE_ERROR_SECURITY;
+ }
+ // Post the result since a real validator must do work asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result));
+ }
+
+ private:
+ base::File::Error result_;
+ base::File::Error write_result_;
+ std::string reject_string_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestValidator);
+ };
+};
+
+// Records CopyProgressCallback invocations.
+struct ProgressRecord {
+ storage::FileSystemOperation::CopyProgressType type;
+ FileSystemURL source_url;
+ FileSystemURL dest_url;
+ int64_t size;
+};
+
+void RecordProgressCallback(std::vector<ProgressRecord>* records,
+ storage::FileSystemOperation::CopyProgressType type,
+ const FileSystemURL& source_url,
+ const FileSystemURL& dest_url,
+ int64_t size) {
+ ProgressRecord record;
+ record.type = type;
+ record.source_url = source_url;
+ record.dest_url = dest_url;
+ record.size = size;
+ records->push_back(record);
+}
+
+void RecordFileProgressCallback(std::vector<int64_t>* records,
+ int64_t progress) {
+ records->push_back(progress);
+}
+
+void AssignAndQuit(base::RunLoop* run_loop,
+ base::File::Error* result_out,
+ base::File::Error result) {
+ *result_out = result;
+ run_loop->Quit();
+}
+
+class ScopedThreadStopper {
+ public:
+ ScopedThreadStopper(base::Thread* thread) : thread_(thread) {
+ }
+
+ ~ScopedThreadStopper() {
+ if (thread_) {
+ // Give another chance for deleted streams to perform Close.
+ base::RunLoop run_loop;
+ thread_->task_runner()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&base::DoNothing), run_loop.QuitClosure());
+ run_loop.Run();
+ thread_->Stop();
+ }
+ }
+
+ bool is_valid() const { return thread_; }
+
+ private:
+ base::Thread* thread_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedThreadStopper);
+};
+
+} // namespace
+
+class CopyOrMoveOperationTestHelper {
+ public:
+ CopyOrMoveOperationTestHelper(const GURL& origin,
+ storage::FileSystemType src_type,
+ storage::FileSystemType dest_type)
+ : origin_(origin), src_type_(src_type), dest_type_(dest_type) {}
+
+ ~CopyOrMoveOperationTestHelper() {
+ file_system_context_ = NULL;
+ quota_manager_proxy_->SimulateQuotaManagerDestroyed();
+ quota_manager_ = NULL;
+ quota_manager_proxy_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void SetUp() {
+ SetUp(true, true);
+ }
+
+ void SetUpNoValidator() {
+ SetUp(true, false);
+ }
+
+ void SetUp(bool require_copy_or_move_validator,
+ bool init_copy_or_move_validator) {
+ ASSERT_TRUE(base_.CreateUniqueTempDir());
+ base::FilePath base_dir = base_.GetPath();
+ quota_manager_ =
+ new MockQuotaManager(false /* is_incognito */, base_dir,
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ NULL /* special storage policy */);
+ quota_manager_proxy_ = new MockQuotaManagerProxy(
+ quota_manager_.get(), base::ThreadTaskRunnerHandle::Get().get());
+ file_system_context_ =
+ CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir);
+
+ // Prepare the origin's root directory.
+ storage::FileSystemBackend* backend =
+ file_system_context_->GetFileSystemBackend(src_type_);
+ backend->ResolveURL(
+ FileSystemURL::CreateForTest(origin_, src_type_, base::FilePath()),
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ backend = file_system_context_->GetFileSystemBackend(dest_type_);
+ if (dest_type_ == storage::kFileSystemTypeTest) {
+ TestFileSystemBackend* test_backend =
+ static_cast<TestFileSystemBackend*>(backend);
+ std::unique_ptr<storage::CopyOrMoveFileValidatorFactory> factory(
+ new TestValidatorFactory);
+ test_backend->set_require_copy_or_move_validator(
+ require_copy_or_move_validator);
+ if (init_copy_or_move_validator)
+ test_backend->InitializeCopyOrMoveFileValidatorFactory(
+ std::move(factory));
+ }
+ backend->ResolveURL(
+ FileSystemURL::CreateForTest(origin_, dest_type_, base::FilePath()),
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ base::RunLoop().RunUntilIdle();
+
+ // Grant relatively big quota initially.
+ quota_manager_->SetQuota(
+ origin_,
+ storage::FileSystemTypeToQuotaStorageType(src_type_),
+ 1024 * 1024);
+ quota_manager_->SetQuota(
+ origin_,
+ storage::FileSystemTypeToQuotaStorageType(dest_type_),
+ 1024 * 1024);
+ }
+
+ int64_t GetSourceUsage() {
+ int64_t usage = 0;
+ GetUsageAndQuota(src_type_, &usage, NULL);
+ return usage;
+ }
+
+ int64_t GetDestUsage() {
+ int64_t usage = 0;
+ GetUsageAndQuota(dest_type_, &usage, NULL);
+ return usage;
+ }
+
+ FileSystemURL SourceURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, src_type_, base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ FileSystemURL DestURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, dest_type_, base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ base::File::Error Copy(const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest);
+ }
+
+ base::File::Error CopyWithProgress(
+ const FileSystemURL& src,
+ const FileSystemURL& dest,
+ const AsyncFileTestHelper::CopyProgressCallback& progress_callback) {
+ return AsyncFileTestHelper::CopyWithProgress(
+ file_system_context_.get(), src, dest, progress_callback);
+ }
+
+ base::File::Error Move(const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest);
+ }
+
+ base::File::Error SetUpTestCaseFiles(
+ const FileSystemURL& root,
+ const FileSystemTestCaseRecord* const test_cases,
+ size_t test_case_size) {
+ base::File::Error result = base::File::FILE_ERROR_FAILED;
+ for (size_t i = 0; i < test_case_size; ++i) {
+ const FileSystemTestCaseRecord& test_case = test_cases[i];
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ root.origin(),
+ root.mount_type(),
+ root.virtual_path().Append(test_case.path));
+ if (test_case.is_directory)
+ result = CreateDirectory(url);
+ else
+ result = CreateFile(url, test_case.data_file_size);
+ EXPECT_EQ(base::File::FILE_OK, result) << url.DebugString();
+ if (result != base::File::FILE_OK)
+ return result;
+ }
+ return result;
+ }
+
+ void VerifyTestCaseFiles(
+ const FileSystemURL& root,
+ const FileSystemTestCaseRecord* const test_cases,
+ size_t test_case_size) {
+ std::map<base::FilePath, const FileSystemTestCaseRecord*> test_case_map;
+ for (size_t i = 0; i < test_case_size; ++i) {
+ test_case_map[
+ base::FilePath(test_cases[i].path).NormalizePathSeparators()] =
+ &test_cases[i];
+ }
+
+ std::queue<FileSystemURL> directories;
+ FileEntryList entries;
+ directories.push(root);
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+ ASSERT_EQ(base::File::FILE_OK, ReadDirectory(dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ dir.origin(),
+ dir.mount_type(),
+ dir.virtual_path().Append(entries[i].name));
+ base::FilePath relative;
+ root.virtual_path().AppendRelativePath(url.virtual_path(), &relative);
+ relative = relative.NormalizePathSeparators();
+ ASSERT_TRUE(base::ContainsKey(test_case_map, relative));
+ if (entries[i].is_directory) {
+ EXPECT_TRUE(test_case_map[relative]->is_directory);
+ directories.push(url);
+ } else {
+ EXPECT_FALSE(test_case_map[relative]->is_directory);
+ EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size));
+ }
+ test_case_map.erase(relative);
+ }
+ }
+ EXPECT_TRUE(test_case_map.empty());
+ std::map<base::FilePath,
+ const FileSystemTestCaseRecord*>::const_iterator it;
+ for (it = test_case_map.begin(); it != test_case_map.end(); ++it) {
+ LOG(ERROR) << "Extra entry: " << it->first.LossyDisplayName();
+ }
+ }
+
+ base::File::Error ReadDirectory(const FileSystemURL& url,
+ FileEntryList* entries) {
+ return AsyncFileTestHelper::ReadDirectory(
+ file_system_context_.get(), url, entries);
+ }
+
+ base::File::Error CreateDirectory(const FileSystemURL& url) {
+ return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
+ url);
+ }
+
+ base::File::Error CreateFile(const FileSystemURL& url, size_t size) {
+ base::File::Error result =
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
+ if (result != base::File::FILE_OK)
+ return result;
+ return AsyncFileTestHelper::TruncateFile(
+ file_system_context_.get(), url, size);
+ }
+
+ bool FileExists(const FileSystemURL& url, int64_t expected_size) {
+ return AsyncFileTestHelper::FileExists(
+ file_system_context_.get(), url, expected_size);
+ }
+
+ bool DirectoryExists(const FileSystemURL& url) {
+ return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(),
+ url);
+ }
+
+ private:
+ void GetUsageAndQuota(storage::FileSystemType type,
+ int64_t* usage,
+ int64_t* quota) {
+ storage::QuotaStatusCode status = AsyncFileTestHelper::GetUsageAndQuota(
+ quota_manager_.get(), origin_, type, usage, quota);
+ ASSERT_EQ(storage::kQuotaStatusOk, status);
+ }
+
+ private:
+ base::ScopedTempDir base_;
+
+ const GURL origin_;
+ const storage::FileSystemType src_type_;
+ const storage::FileSystemType dest_type_;
+
+ base::MessageLoopForIO message_loop_;
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+ scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
+ scoped_refptr<MockQuotaManager> quota_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationTestHelper);
+};
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFile) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source file.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
+
+ // Verify.
+ ASSERT_TRUE(helper.FileExists(src, 10));
+ ASSERT_TRUE(helper.FileExists(dest, 10));
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleFile) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source file.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.FileExists(src, AsyncFileTestHelper::kDontCheckSize));
+ ASSERT_TRUE(helper.FileExists(dest, 10));
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.CopyWithProgress(
+ src, dest,
+ AsyncFileTestHelper::CopyProgressCallback()));
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ helper.VerifyTestCaseFiles(dest,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize);
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64_t src_initial_usage = helper.GetSourceUsage();
+ int64_t dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize));
+ int64_t src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ helper.VerifyTestCaseFiles(dest,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize);
+
+ int64_t src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64_t dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest,
+ MoveDirectoryFailPostWriteValidation) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypeTest);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize));
+
+ // Move it.
+ helper.Move(src, dest);
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ // In the move operation, [file 0, file 2, file 3] are processed as LIFO.
+ // After file 3 is processed, file 2 is rejected by the validator and the
+ // operation fails. That is, only file 3 should be in dest.
+ FileSystemTestCaseRecord kMoveDirResultCases[] = {
+ {false, FILE_PATH_LITERAL("file 3"), 0},
+ };
+
+ helper.VerifyTestCaseFiles(dest,
+ kMoveDirResultCases,
+ arraysize(kMoveDirResultCases));
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypeTest);
+ helper.SetUpNoValidator();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+
+ // Set up a source file.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
+
+ // The copy attempt should fail with a security error -- getting
+ // the factory returns a security error, and the copy operation must
+ // respect that.
+ ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.Copy(src, dest));
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, ProgressCallback) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ storage::kFileSystemTypeTemporary,
+ storage::kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+
+ // Set up a source directory.
+ ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ kRegularFileSystemTestCases,
+ kRegularFileSystemTestCaseSize));
+
+ std::vector<ProgressRecord> records;
+ ASSERT_EQ(base::File::FILE_OK,
+ helper.CopyWithProgress(src, dest,
+ base::Bind(&RecordProgressCallback,
+ base::Unretained(&records))));
+
+ // Verify progress callback.
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i];
+
+ FileSystemURL src_url = helper.SourceURL(
+ std::string("a/") + base::FilePath(test_case.path).AsUTF8Unsafe());
+ FileSystemURL dest_url = helper.DestURL(
+ std::string("b/") + base::FilePath(test_case.path).AsUTF8Unsafe());
+
+ // Find the first and last progress record.
+ size_t begin_index = records.size();
+ size_t end_index = records.size();
+ for (size_t j = 0; j < records.size(); ++j) {
+ if (records[j].source_url == src_url) {
+ if (begin_index == records.size())
+ begin_index = j;
+ end_index = j;
+ }
+ }
+
+ // The record should be found.
+ ASSERT_NE(begin_index, records.size());
+ ASSERT_NE(end_index, records.size());
+ ASSERT_NE(begin_index, end_index);
+
+ EXPECT_EQ(FileSystemOperation::BEGIN_COPY_ENTRY,
+ records[begin_index].type);
+ EXPECT_FALSE(records[begin_index].dest_url.is_valid());
+ EXPECT_EQ(FileSystemOperation::END_COPY_ENTRY, records[end_index].type);
+ EXPECT_EQ(dest_url, records[end_index].dest_url);
+
+ if (test_case.is_directory) {
+ // For directory copy, the progress shouldn't be interlaced.
+ EXPECT_EQ(begin_index + 1, end_index);
+ } else {
+ // PROGRESS event's size should be assending order.
+ int64_t current_size = 0;
+ for (size_t j = begin_index + 1; j < end_index; ++j) {
+ if (records[j].source_url == src_url) {
+ EXPECT_EQ(FileSystemOperation::PROGRESS, records[j].type);
+ EXPECT_FALSE(records[j].dest_url.is_valid());
+ EXPECT_GE(records[j].size, current_size);
+ current_size = records[j].size;
+ }
+ }
+ }
+ }
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
+ base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
+ const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ base::WriteFile(source_path, kTestData,
+ arraysize(kTestData) - 1); // Exclude trailing '\0'.
+
+ base::MessageLoopForIO message_loop;
+ base::Thread file_thread("file_thread");
+ ASSERT_TRUE(file_thread.Start());
+ ScopedThreadStopper thread_stopper(&file_thread);
+ ASSERT_TRUE(thread_stopper.is_valid());
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+ file_thread.task_runner();
+
+ std::unique_ptr<storage::FileStreamReader> reader(
+ storage::FileStreamReader::CreateForLocalFile(
+ task_runner.get(), source_path, 0, base::Time()));
+
+ std::unique_ptr<FileStreamWriter> writer(FileStreamWriter::CreateForLocalFile(
+ task_runner.get(), dest_path, 0, FileStreamWriter::CREATE_NEW_FILE));
+
+ std::vector<int64_t> progress;
+ CopyOrMoveOperationDelegate::StreamCopyHelper helper(
+ std::move(reader), std::move(writer),
+ storage::FlushPolicy::NO_FLUSH_ON_COMPLETION,
+ 10, // buffer size
+ base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
+ base::TimeDelta()); // For testing, we need all the progress.
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
+ run_loop.Run();
+
+ EXPECT_EQ(base::File::FILE_OK, error);
+ ASSERT_EQ(5U, progress.size());
+ EXPECT_EQ(0, progress[0]);
+ EXPECT_EQ(10, progress[1]);
+ EXPECT_EQ(20, progress[2]);
+ EXPECT_EQ(30, progress[3]);
+ EXPECT_EQ(36, progress[4]);
+
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
+ EXPECT_EQ(kTestData, content);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelperWithFlush) {
+ // Testing the same configuration as StreamCopyHelper, but with |need_flush|
+ // parameter set to true. Since it is hard to test that the flush is indeed
+ // taking place, this test just only verifies that the file is correctly
+ // written with or without the flag.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
+ base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
+ const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ base::WriteFile(source_path, kTestData,
+ arraysize(kTestData) - 1); // Exclude trailing '\0'.
+
+
+ base::MessageLoopForIO message_loop;
+ base::Thread file_thread("file_thread");
+ ASSERT_TRUE(file_thread.Start());
+ ScopedThreadStopper thread_stopper(&file_thread);
+ ASSERT_TRUE(thread_stopper.is_valid());
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+ file_thread.task_runner();
+
+ std::unique_ptr<storage::FileStreamReader> reader(
+ storage::FileStreamReader::CreateForLocalFile(
+ task_runner.get(), source_path, 0, base::Time()));
+
+ std::unique_ptr<FileStreamWriter> writer(FileStreamWriter::CreateForLocalFile(
+ task_runner.get(), dest_path, 0, FileStreamWriter::CREATE_NEW_FILE));
+
+ std::vector<int64_t> progress;
+ CopyOrMoveOperationDelegate::StreamCopyHelper helper(
+ std::move(reader), std::move(writer),
+ storage::FlushPolicy::NO_FLUSH_ON_COMPLETION,
+ 10, // buffer size
+ base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
+ base::TimeDelta()); // For testing, we need all the progress.
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
+ run_loop.Run();
+
+ EXPECT_EQ(base::File::FILE_OK, error);
+ ASSERT_EQ(5U, progress.size());
+ EXPECT_EQ(0, progress[0]);
+ EXPECT_EQ(10, progress[1]);
+ EXPECT_EQ(20, progress[2]);
+ EXPECT_EQ(30, progress[3]);
+ EXPECT_EQ(36, progress[4]);
+
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
+ EXPECT_EQ(kTestData, content);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper_Cancel) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath source_path = temp_dir.GetPath().AppendASCII("source");
+ base::FilePath dest_path = temp_dir.GetPath().AppendASCII("dest");
+ const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ base::WriteFile(source_path, kTestData,
+ arraysize(kTestData) - 1); // Exclude trailing '\0'.
+
+ base::MessageLoopForIO message_loop;
+ base::Thread file_thread("file_thread");
+ ASSERT_TRUE(file_thread.Start());
+ ScopedThreadStopper thread_stopper(&file_thread);
+ ASSERT_TRUE(thread_stopper.is_valid());
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+ file_thread.task_runner();
+
+ std::unique_ptr<storage::FileStreamReader> reader(
+ storage::FileStreamReader::CreateForLocalFile(
+ task_runner.get(), source_path, 0, base::Time()));
+
+ std::unique_ptr<FileStreamWriter> writer(FileStreamWriter::CreateForLocalFile(
+ task_runner.get(), dest_path, 0, FileStreamWriter::CREATE_NEW_FILE));
+
+ std::vector<int64_t> progress;
+ CopyOrMoveOperationDelegate::StreamCopyHelper helper(
+ std::move(reader), std::move(writer),
+ storage::FlushPolicy::NO_FLUSH_ON_COMPLETION,
+ 10, // buffer size
+ base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
+ base::TimeDelta()); // For testing, we need all the progress.
+
+ // Call Cancel() later.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel,
+ base::Unretained(&helper)));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
+ run_loop.Run();
+
+ EXPECT_EQ(base::File::FILE_ERROR_ABORT, error);
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/dragged_file_util_unittest.cc b/chromium/storage/browser/fileapi/dragged_file_util_unittest.cc
new file mode 100644
index 00000000000..5d53cfcb8d6
--- /dev/null
+++ b/chromium/storage/browser/fileapi/dragged_file_util_unittest.cc
@@ -0,0 +1,554 @@
+// Copyright 2013 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 <stddef.h>
+
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "storage/browser/fileapi/dragged_file_util.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/local_file_util.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/fileapi_test_file_set.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemContext;
+using storage::FileSystemOperationContext;
+using storage::FileSystemType;
+using storage::FileSystemURL;
+
+namespace content {
+
+namespace {
+
+typedef AsyncFileTestHelper::FileEntryList FileEntryList;
+
+// Used in DraggedFileUtilTest::SimulateDropFiles().
+// Random root paths in which we create each file/directory of the
+// RegularTestCases (so that we can simulate a drop with files/directories
+// from multiple directories).
+static const base::FilePath::CharType* kRootPaths[] = {
+ FILE_PATH_LITERAL("a"),
+ FILE_PATH_LITERAL("b/c"),
+ FILE_PATH_LITERAL("etc"),
+};
+
+base::FilePath GetTopLevelPath(const base::FilePath& path) {
+ std::vector<base::FilePath::StringType> components;
+ path.GetComponents(&components);
+ return base::FilePath(components[0]);
+}
+
+bool IsDirectoryEmpty(FileSystemContext* context, const FileSystemURL& url) {
+ FileEntryList entries;
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(context, url, &entries));
+ return entries.empty();
+}
+
+FileSystemURL GetEntryURL(FileSystemContext* file_system_context,
+ const FileSystemURL& dir,
+ const base::FilePath::StringType& name) {
+ return file_system_context->CreateCrackedFileSystemURL(
+ dir.origin(),
+ dir.mount_type(),
+ dir.virtual_path().Append(name));
+}
+
+base::FilePath GetRelativeVirtualPath(const FileSystemURL& root,
+ const FileSystemURL& url) {
+ if (root.virtual_path().empty())
+ return url.virtual_path();
+ base::FilePath relative;
+ const bool success = root.virtual_path().AppendRelativePath(
+ url.virtual_path(), &relative);
+ DCHECK(success);
+ return relative;
+}
+
+FileSystemURL GetOtherURL(FileSystemContext* file_system_context,
+ const FileSystemURL& root,
+ const FileSystemURL& other_root,
+ const FileSystemURL& url) {
+ return file_system_context->CreateCrackedFileSystemURL(
+ other_root.origin(),
+ other_root.mount_type(),
+ other_root.virtual_path().Append(GetRelativeVirtualPath(root, url)));
+}
+
+} // namespace
+
+class DraggedFileUtilTest : public testing::Test {
+ public:
+ DraggedFileUtilTest() {}
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(partition_dir_.CreateUniqueTempDir());
+ file_util_.reset(new storage::DraggedFileUtil());
+
+ // Register the files/directories of RegularTestCases (with random
+ // root paths) as dropped files.
+ SimulateDropFiles();
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL /* quota_manager */, partition_dir_.GetPath());
+
+ isolated_context()->AddReference(filesystem_id_);
+ }
+
+ void TearDown() override {
+ isolated_context()->RemoveReference(filesystem_id_);
+ }
+
+ protected:
+ storage::IsolatedContext* isolated_context() const {
+ return storage::IsolatedContext::GetInstance();
+ }
+ const base::FilePath& root_path() const { return data_dir_.GetPath(); }
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+ storage::FileSystemFileUtil* file_util() const { return file_util_.get(); }
+ std::string filesystem_id() const { return filesystem_id_; }
+
+ base::FilePath GetTestCasePlatformPath(
+ const base::FilePath::StringType& path) {
+ return toplevel_root_map_[GetTopLevelPath(base::FilePath(path))]
+ .Append(path).NormalizePathSeparators();
+ }
+
+ base::FilePath GetTestCaseLocalPath(const base::FilePath& path) {
+ base::FilePath relative;
+ if (data_dir_.GetPath().AppendRelativePath(path, &relative))
+ return relative;
+ return path;
+ }
+
+ FileSystemURL GetFileSystemURL(const base::FilePath& path) const {
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(
+ filesystem_id()).Append(path);
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://example.com"),
+ storage::kFileSystemTypeIsolated,
+ virtual_path);
+ }
+
+ FileSystemURL GetOtherFileSystemURL(const base::FilePath& path) const {
+ return file_system_context()->CreateCrackedFileSystemURL(
+ GURL("http://example.com"),
+ storage::kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII("dest").Append(path));
+ }
+
+ void VerifyFilesHaveSameContent(const FileSystemURL& url1,
+ const FileSystemURL& url2) {
+ // Get the file info and the platform path for url1.
+ base::File::Info info1;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context(), url1, &info1));
+ base::FilePath platform_path1;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetPlatformPath(
+ file_system_context(), url1, &platform_path1));
+
+ // Get the file info and the platform path for url2.
+ base::File::Info info2;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context(), url2, &info2));
+ base::FilePath platform_path2;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetPlatformPath(
+ file_system_context(), url2, &platform_path2));
+
+ // See if file info matches with the other one.
+ EXPECT_EQ(info1.is_directory, info2.is_directory);
+ EXPECT_EQ(info1.size, info2.size);
+ EXPECT_EQ(info1.is_symbolic_link, info2.is_symbolic_link);
+ EXPECT_NE(platform_path1, platform_path2);
+
+ std::string content1, content2;
+ EXPECT_TRUE(base::ReadFileToString(platform_path1, &content1));
+ EXPECT_TRUE(base::ReadFileToString(platform_path2, &content2));
+ EXPECT_EQ(content1, content2);
+ }
+
+ void VerifyDirectoriesHaveSameContent(const FileSystemURL& root1,
+ const FileSystemURL& root2) {
+ base::FilePath root_path1 = root1.path();
+ base::FilePath root_path2 = root2.path();
+
+ FileEntryList entries;
+ std::queue<FileSystemURL> directories;
+
+ directories.push(root1);
+ std::set<base::FilePath> file_set1;
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ if (entries[i].is_directory) {
+ directories.push(url);
+ continue;
+ }
+ file_set1.insert(GetRelativeVirtualPath(root1, url));
+ }
+ }
+
+ directories.push(root2);
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url2 = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ FileSystemURL url1 = GetOtherURL(file_system_context(),
+ root2, root1, url2);
+ if (entries[i].is_directory) {
+ directories.push(url2);
+ EXPECT_EQ(IsDirectoryEmpty(file_system_context(), url1),
+ IsDirectoryEmpty(file_system_context(), url2));
+ continue;
+ }
+ base::FilePath relative = GetRelativeVirtualPath(root2, url2);
+ EXPECT_TRUE(file_set1.find(relative) != file_set1.end());
+ VerifyFilesHaveSameContent(url1, url2);
+ }
+ }
+ }
+
+ std::unique_ptr<storage::FileSystemOperationContext> GetOperationContext() {
+ return base::MakeUnique<storage::FileSystemOperationContext>(
+ file_system_context());
+ }
+
+
+ private:
+ void SimulateDropFiles() {
+ size_t root_path_index = 0;
+
+ storage::IsolatedContext::FileInfoSet toplevels;
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ base::FilePath path(test_case.path);
+ base::FilePath toplevel = GetTopLevelPath(path);
+
+ // We create the test case files under one of the kRootPaths
+ // to simulate a drop with multiple directories.
+ if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) {
+ base::FilePath root = root_path().Append(
+ kRootPaths[(root_path_index++) % arraysize(kRootPaths)]);
+ toplevel_root_map_[toplevel] = root;
+ toplevels.AddPath(root.Append(path), NULL);
+ }
+
+ SetUpOneFileSystemTestCase(toplevel_root_map_[toplevel], test_case);
+ }
+
+ // Register the toplevel entries.
+ filesystem_id_ = isolated_context()->RegisterDraggedFileSystem(toplevels);
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::ScopedTempDir partition_dir_;
+ base::MessageLoopForIO message_loop_;
+ std::string filesystem_id_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ std::map<base::FilePath, base::FilePath> toplevel_root_map_;
+ std::unique_ptr<storage::DraggedFileUtil> file_util_;
+ DISALLOW_COPY_AND_ASSIGN(DraggedFileUtilTest);
+};
+
+TEST_F(DraggedFileUtilTest, BasicTest) {
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i);
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // See if we can query the file info via the isolated FileUtil.
+ // (This should succeed since we have registered all the top-level
+ // entries of the test cases in SetUp())
+ base::File::Info info;
+ base::FilePath platform_path;
+ FileSystemOperationContext context(file_system_context());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->GetFileInfo(&context, url, &info, &platform_path));
+
+ // See if the obtained file info is correct.
+ if (!test_case.is_directory)
+ ASSERT_EQ(test_case.data_file_size, info.size);
+ ASSERT_EQ(test_case.is_directory, info.is_directory);
+ ASSERT_EQ(GetTestCasePlatformPath(test_case.path),
+ platform_path.NormalizePathSeparators());
+ }
+}
+
+TEST_F(DraggedFileUtilTest, UnregisteredPathsTest) {
+ static const FileSystemTestCaseRecord kUnregisteredCases[] = {
+ {true, FILE_PATH_LITERAL("nonexistent"), 0},
+ {true, FILE_PATH_LITERAL("nonexistent/dir foo"), 0},
+ {false, FILE_PATH_LITERAL("nonexistent/false"), 0},
+ {false, FILE_PATH_LITERAL("foo"), 30},
+ {false, FILE_PATH_LITERAL("bar"), 20},
+ };
+
+ for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i);
+ const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i];
+
+ // Prepare the test file/directory.
+ SetUpOneFileSystemTestCase(root_path(), test_case);
+
+ // Make sure regular GetFileInfo succeeds.
+ base::File::Info info;
+ ASSERT_TRUE(base::GetFileInfo(root_path().Append(test_case.path), &info));
+ if (!test_case.is_directory)
+ ASSERT_EQ(test_case.data_file_size, info.size);
+ ASSERT_EQ(test_case.is_directory, info.is_directory);
+ }
+
+ for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i);
+ const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i];
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // We should not be able to get the valid URL for unregistered files.
+ ASSERT_FALSE(url.is_valid());
+ }
+}
+
+TEST_F(DraggedFileUtilTest, ReadDirectoryTest) {
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ if (!test_case.is_directory)
+ continue;
+
+ SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i
+ << ": " << test_case.path);
+
+ // Read entries in the directory to construct the expected results map.
+ typedef std::map<base::FilePath::StringType, storage::DirectoryEntry>
+ EntryMap;
+ EntryMap expected_entry_map;
+
+ base::FilePath dir_path = GetTestCasePlatformPath(test_case.path);
+ base::FileEnumerator file_enum(
+ dir_path, false /* not recursive */,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ base::FilePath current;
+ while (!(current = file_enum.Next()).empty()) {
+ base::FileEnumerator::FileInfo file_info = file_enum.GetInfo();
+ storage::DirectoryEntry entry;
+ entry.is_directory = file_info.IsDirectory();
+ entry.name = current.BaseName().value();
+ expected_entry_map[entry.name] = entry;
+
+#if defined(OS_POSIX)
+ // Creates a symlink for each file/directory.
+ // They should be ignored by ReadDirectory, so we don't add them
+ // to expected_entry_map.
+ base::CreateSymbolicLink(
+ current,
+ dir_path.Append(current.BaseName().AddExtension(
+ FILE_PATH_LITERAL("link"))));
+#endif
+ }
+
+ // Perform ReadDirectory in the isolated filesystem.
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+ FileEntryList entries;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), url, &entries));
+
+ EXPECT_EQ(expected_entry_map.size(), entries.size());
+ for (size_t i = 0; i < entries.size(); ++i) {
+ const storage::DirectoryEntry& entry = entries[i];
+ EntryMap::iterator found = expected_entry_map.find(entry.name);
+ EXPECT_TRUE(found != expected_entry_map.end());
+ EXPECT_EQ(found->second.name, entry.name);
+ EXPECT_EQ(found->second.is_directory, entry.is_directory);
+ }
+ }
+}
+
+TEST_F(DraggedFileUtilTest, GetLocalFilePathTest) {
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ FileSystemOperationContext context(file_system_context());
+
+ base::FilePath local_file_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->GetLocalFilePath(&context, url, &local_file_path));
+ EXPECT_EQ(GetTestCasePlatformPath(test_case.path).value(),
+ local_file_path.value());
+ }
+}
+
+TEST_F(DraggedFileUtilTest, CopyOutFileTest) {
+ FileSystemURL src_root = GetFileSystemURL(base::FilePath());
+ FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath());
+
+ FileEntryList entries;
+ std::queue<FileSystemURL> directories;
+ directories.push(src_root);
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_root));
+
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(),
+ dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL src_url = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ FileSystemURL dest_url = GetOtherURL(file_system_context(),
+ src_root, dest_root, src_url);
+
+ if (entries[i].is_directory) {
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_url));
+ directories.push(src_url);
+ continue;
+ }
+ SCOPED_TRACE(testing::Message() << "Testing file copy "
+ << src_url.path().value());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ src_url, dest_url));
+ VerifyFilesHaveSameContent(src_url, dest_url);
+ }
+ }
+}
+
+TEST_F(DraggedFileUtilTest, CopyOutDirectoryTest) {
+ FileSystemURL src_root = GetFileSystemURL(base::FilePath());
+ FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_root));
+
+ FileEntryList entries;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(),
+ src_root, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ if (!entries[i].is_directory)
+ continue;
+ FileSystemURL src_url = GetEntryURL(file_system_context(),
+ src_root, entries[i].name);
+ FileSystemURL dest_url = GetOtherURL(file_system_context(),
+ src_root, dest_root, src_url);
+ SCOPED_TRACE(testing::Message() << "Testing file copy "
+ << src_url.path().value());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ src_url, dest_url));
+ VerifyDirectoriesHaveSameContent(src_url, dest_url);
+ }
+}
+
+TEST_F(DraggedFileUtilTest, TouchTest) {
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ if (test_case.is_directory)
+ continue;
+ SCOPED_TRACE(testing::Message() << test_case.path);
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ base::Time last_access_time = base::Time::FromTimeT(1000);
+ base::Time last_modified_time = base::Time::FromTimeT(2000);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->Touch(GetOperationContext().get(), url,
+ last_access_time,
+ last_modified_time));
+
+ // Verification.
+ base::File::Info info;
+ base::FilePath platform_path;
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(last_access_time.ToTimeT(), info.last_accessed.ToTimeT());
+ EXPECT_EQ(last_modified_time.ToTimeT(), info.last_modified.ToTimeT());
+ }
+}
+
+TEST_F(DraggedFileUtilTest, TruncateTest) {
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ if (test_case.is_directory)
+ continue;
+
+ SCOPED_TRACE(testing::Message() << test_case.path);
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // Truncate to 0.
+ base::File::Info info;
+ base::FilePath platform_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(GetOperationContext().get(), url, 0));
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(0, info.size);
+
+ // Truncate (extend) to 999.
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(GetOperationContext().get(), url, 999));
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(999, info.size);
+ }
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_context.cc b/chromium/storage/browser/fileapi/file_system_context.cc
index 1de97d456e4..416e7edc57e 100644
--- a/chromium/storage/browser/fileapi/file_system_context.cc
+++ b/chromium/storage/browser/fileapi/file_system_context.cc
@@ -208,7 +208,7 @@ FileSystemContext::FileSystemContext(
bool FileSystemContext::DeleteDataForOriginOnFileTaskRunner(
const GURL& origin_url) {
- DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(default_file_task_runner()->RunsTasksInCurrentSequence());
DCHECK(origin_url == origin_url.GetOrigin());
bool success = true;
@@ -233,7 +233,7 @@ scoped_refptr<QuotaReservation>
FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
const GURL& origin_url,
FileSystemType type) {
- DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(default_file_task_runner()->RunsTasksInCurrentSequence());
FileSystemBackend* backend = GetFileSystemBackend(type);
if (!backend || !backend->GetQuotaUtil())
return scoped_refptr<QuotaReservation>();
@@ -242,7 +242,7 @@ FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
}
void FileSystemContext::Shutdown() {
- if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ if (!io_task_runner_->RunsTasksInCurrentSequence()) {
io_task_runner_->PostTask(
FROM_HERE, base::Bind(&FileSystemContext::Shutdown,
make_scoped_refptr(this)));
@@ -338,7 +338,7 @@ void FileSystemContext::OpenFileSystem(
FileSystemType type,
OpenFileSystemMode mode,
const OpenFileSystemCallback& callback) {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!callback.is_null());
if (!FileSystemContext::IsSandboxFileSystem(type)) {
@@ -365,7 +365,7 @@ void FileSystemContext::ResolveURL(
DCHECK(!callback.is_null());
// If not on IO thread, forward before passing the task to the backend.
- if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ if (!io_task_runner_->RunsTasksInCurrentSequence()) {
ResolveURLCallback relay_callback =
base::Bind(&RelayResolveURLCallback,
base::ThreadTaskRunnerHandle::Get(), callback);
@@ -412,7 +412,7 @@ void FileSystemContext::DeleteFileSystem(
const GURL& origin_url,
FileSystemType type,
const StatusCallback& callback) {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin_url == origin_url.GetOrigin());
DCHECK(!callback.is_null());
@@ -510,8 +510,8 @@ void FileSystemContext::OpenPluginPrivateFileSystem(
FileSystemContext::~FileSystemContext() {
}
-void FileSystemContext::DeleteOnCorrectThread() const {
- if (!io_task_runner_->RunsTasksOnCurrentThread() &&
+void FileSystemContext::DeleteOnCorrectSequence() const {
+ if (!io_task_runner_->RunsTasksInCurrentSequence() &&
io_task_runner_->DeleteSoon(FROM_HERE, this)) {
return;
}
@@ -602,7 +602,7 @@ void FileSystemContext::DidOpenFileSystemForResolveURL(
const GURL& filesystem_root,
const std::string& filesystem_name,
base::File::Error error) {
- DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
if (error != base::File::FILE_OK) {
callback.Run(error, FileSystemInfo(), base::FilePath(),
diff --git a/chromium/storage/browser/fileapi/file_system_context.h b/chromium/storage/browser/fileapi/file_system_context.h
index 93a4825e0aa..c7190f2de54 100644
--- a/chromium/storage/browser/fileapi/file_system_context.h
+++ b/chromium/storage/browser/fileapi/file_system_context.h
@@ -94,8 +94,8 @@ class STORAGE_EXPORT FileSystemContext
// file_task_runner is used as default TaskRunner.
// Unless a FileSystemBackend is overridden in CreateFileSystemOperation,
// it is used for all file operations and file related meta operations.
- // The code assumes that file_task_runner->RunsTasksOnCurrentThread()
- // returns false if the current task is not running on the thread that allows
+ // The code assumes that file_task_runner->RunsTasksInCurrentSequence()
+ // returns false if the current task is not running on the sequence that allows
// blocking file operations (like SequencedWorkerPool implementation does).
//
// |external_mount_points| contains non-system external mount points available
@@ -321,7 +321,7 @@ class STORAGE_EXPORT FileSystemContext
DefaultContextDeleter>;
~FileSystemContext();
- void DeleteOnCorrectThread() const;
+ void DeleteOnCorrectSequence() const;
// Creates a new FileSystemOperation instance by getting an appropriate
// FileSystemBackend for |url| and calling the backend's corresponding
@@ -409,7 +409,7 @@ class STORAGE_EXPORT FileSystemContext
struct DefaultContextDeleter {
static void Destruct(const FileSystemContext* context) {
- context->DeleteOnCorrectThread();
+ context->DeleteOnCorrectSequence();
}
};
diff --git a/chromium/storage/browser/fileapi/file_system_context_unittest.cc b/chromium/storage/browser/fileapi/file_system_context_unittest.cc
new file mode 100644
index 00000000000..44b5e4e30be
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_context_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright 2013 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 "storage/browser/fileapi/file_system_context.h"
+
+#include <stddef.h>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/test/mock_quota_manager.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/test_file_system_options.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define DRIVE FPL("C:")
+#else
+#define DRIVE
+#endif
+
+using storage::ExternalMountPoints;
+using storage::FileSystemBackend;
+using storage::FileSystemContext;
+using storage::FileSystemMountOption;
+using storage::FileSystemURL;
+using storage::IsolatedContext;
+
+namespace content {
+
+namespace {
+
+const char kTestOrigin[] = "http://chromium.org/";
+
+GURL CreateRawFileSystemURL(const std::string& type_str,
+ const std::string& fs_id) {
+ std::string url_str = base::StringPrintf(
+ "filesystem:http://chromium.org/%s/%s/root/file",
+ type_str.c_str(),
+ fs_id.c_str());
+ return GURL(url_str);
+}
+
+class FileSystemContextTest : public testing::Test {
+ public:
+ FileSystemContextTest() {}
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+
+ storage_policy_ = new MockSpecialStoragePolicy();
+
+ mock_quota_manager_ = new MockQuotaManager(
+ false /* is_incognito */, data_dir_.GetPath(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(), storage_policy_.get());
+ }
+
+ protected:
+ FileSystemContext* CreateFileSystemContextForTest(
+ storage::ExternalMountPoints* external_mount_points) {
+ return new FileSystemContext(
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(), external_mount_points,
+ storage_policy_.get(), mock_quota_manager_->proxy(),
+ std::vector<std::unique_ptr<FileSystemBackend>>(),
+ std::vector<storage::URLRequestAutoMountHandler>(), data_dir_.GetPath(),
+ CreateAllowFileAccessOptions());
+ }
+
+ // Verifies a *valid* filesystem url has expected values.
+ void ExpectFileSystemURLMatches(const FileSystemURL& url,
+ const GURL& expect_origin,
+ storage::FileSystemType expect_mount_type,
+ storage::FileSystemType expect_type,
+ const base::FilePath& expect_path,
+ const base::FilePath& expect_virtual_path,
+ const std::string& expect_filesystem_id) {
+ EXPECT_TRUE(url.is_valid());
+
+ EXPECT_EQ(expect_origin, url.origin());
+ EXPECT_EQ(expect_mount_type, url.mount_type());
+ EXPECT_EQ(expect_type, url.type());
+ EXPECT_EQ(expect_path, url.path());
+ EXPECT_EQ(expect_virtual_path, url.virtual_path());
+ EXPECT_EQ(expect_filesystem_id, url.filesystem_id());
+ }
+
+ private:
+ base::ScopedTempDir data_dir_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<storage::SpecialStoragePolicy> storage_policy_;
+ scoped_refptr<MockQuotaManager> mock_quota_manager_;
+};
+
+// It is not valid to pass NULL ExternalMountPoints to FileSystemContext on
+// ChromeOS.
+#if !defined(OS_CHROMEOS)
+TEST_F(FileSystemContextTest, NullExternalMountPoints) {
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(NULL));
+
+ // Cracking system external mount and isolated mount points should work.
+ std::string isolated_name = "root";
+ std::string isolated_id =
+ IsolatedContext::GetInstance()->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal,
+ std::string(),
+ base::FilePath(DRIVE FPL("/test/isolated/root")),
+ &isolated_name);
+ // Register system external mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "system",
+ storage::kFileSystemTypeNativeLocal,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+
+ FileSystemURL cracked_isolated = file_system_context->CrackURL(
+ CreateRawFileSystemURL("isolated", isolated_id));
+
+ ExpectFileSystemURLMatches(
+ cracked_isolated,
+ GURL(kTestOrigin),
+ storage::kFileSystemTypeIsolated,
+ storage::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/isolated/root/file"))
+ .NormalizePathSeparators(),
+ base::FilePath::FromUTF8Unsafe(isolated_id)
+ .Append(FPL("root/file"))
+ .NormalizePathSeparators(),
+ isolated_id);
+
+ FileSystemURL cracked_external = file_system_context->CrackURL(
+ CreateRawFileSystemURL("external", "system"));
+
+ ExpectFileSystemURLMatches(
+ cracked_external,
+ GURL(kTestOrigin),
+ storage::kFileSystemTypeExternal,
+ storage::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/sys/root/file"))
+ .NormalizePathSeparators(),
+ base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
+ "system");
+
+ IsolatedContext::GetInstance()->RevokeFileSystem(isolated_id);
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
+}
+#endif // !defiend(OS_CHROMEOS)
+
+TEST_F(FileSystemContextTest, FileSystemContextKeepsMountPointsAlive) {
+ scoped_refptr<ExternalMountPoints> mount_points =
+ ExternalMountPoints::CreateRefCounted();
+
+ // Register system external mount point.
+ ASSERT_TRUE(mount_points->RegisterFileSystem(
+ "system",
+ storage::kFileSystemTypeNativeLocal,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(mount_points.get()));
+
+ // Release a MountPoints reference created in the test.
+ mount_points = NULL;
+
+ // FileSystemContext should keep a reference to the |mount_points|, so it
+ // should be able to resolve the URL.
+ FileSystemURL cracked_external = file_system_context->CrackURL(
+ CreateRawFileSystemURL("external", "system"));
+
+ ExpectFileSystemURLMatches(
+ cracked_external,
+ GURL(kTestOrigin),
+ storage::kFileSystemTypeExternal,
+ storage::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/sys/root/file"))
+ .NormalizePathSeparators(),
+ base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
+ "system");
+
+ // No need to revoke the registered filesystem since |mount_points| lifetime
+ // is bound to this test.
+}
+
+TEST_F(FileSystemContextTest, CrackFileSystemURL) {
+ scoped_refptr<ExternalMountPoints> external_mount_points(
+ ExternalMountPoints::CreateRefCounted());
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(external_mount_points.get()));
+
+ // Register an isolated mount point.
+ std::string isolated_file_system_name = "root";
+ const std::string kIsolatedFileSystemID =
+ IsolatedContext::GetInstance()->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal,
+ std::string(),
+ base::FilePath(DRIVE FPL("/test/isolated/root")),
+ &isolated_file_system_name);
+ // Register system external mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "system",
+ storage::kFileSystemTypeDrive,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "ext",
+ storage::kFileSystemTypeNativeLocal,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/ext"))));
+ // Register a system external mount point with the same name/id as the
+ // registered isolated mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ kIsolatedFileSystemID,
+ storage::kFileSystemTypeRestrictedNativeLocal,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/system/isolated"))));
+ // Add a mount points with the same name as a system mount point to
+ // FileSystemContext's external mount points.
+ ASSERT_TRUE(external_mount_points->RegisterFileSystem(
+ "ext",
+ storage::kFileSystemTypeNativeLocal,
+ FileSystemMountOption(),
+ base::FilePath(DRIVE FPL("/test/local/ext/"))));
+
+ const GURL kTestOrigin = GURL("http://chromium.org/");
+ const base::FilePath kVirtualPathNoRoot = base::FilePath(FPL("root/file"));
+
+ struct TestCase {
+ // Test case values.
+ std::string root;
+ std::string type_str;
+
+ // Expected test results.
+ bool expect_is_valid;
+ storage::FileSystemType expect_mount_type;
+ storage::FileSystemType expect_type;
+ const base::FilePath::CharType* expect_path;
+ std::string expect_filesystem_id;
+ };
+
+ const TestCase kTestCases[] = {
+ // Following should not be handled by the url crackers:
+ {
+ "pers_mount", "persistent", true /* is_valid */,
+ storage::kFileSystemTypePersistent, storage::kFileSystemTypePersistent,
+ FPL("pers_mount/root/file"), std::string() /* filesystem id */
+ },
+ {
+ "temp_mount", "temporary", true /* is_valid */,
+ storage::kFileSystemTypeTemporary, storage::kFileSystemTypeTemporary,
+ FPL("temp_mount/root/file"), std::string() /* filesystem id */
+ },
+ // Should be cracked by isolated mount points:
+ {kIsolatedFileSystemID, "isolated", true /* is_valid */,
+ storage::kFileSystemTypeIsolated, storage::kFileSystemTypeNativeLocal,
+ DRIVE FPL("/test/isolated/root/file"), kIsolatedFileSystemID},
+ // Should be cracked by system mount points:
+ {"system", "external", true /* is_valid */,
+ storage::kFileSystemTypeExternal, storage::kFileSystemTypeDrive,
+ DRIVE FPL("/test/sys/root/file"), "system"},
+ {kIsolatedFileSystemID, "external", true /* is_valid */,
+ storage::kFileSystemTypeExternal,
+ storage::kFileSystemTypeRestrictedNativeLocal,
+ DRIVE FPL("/test/system/isolated/root/file"), kIsolatedFileSystemID},
+ // Should be cracked by FileSystemContext's ExternalMountPoints.
+ {"ext", "external", true /* is_valid */, storage::kFileSystemTypeExternal,
+ storage::kFileSystemTypeNativeLocal,
+ DRIVE FPL("/test/local/ext/root/file"), "ext"},
+ // Test for invalid filesystem url (made invalid by adding invalid
+ // filesystem type).
+ {"sytem", "external", false /* is_valid */,
+ // The rest of values will be ignored.
+ storage::kFileSystemTypeUnknown, storage::kFileSystemTypeUnknown,
+ FPL(""), std::string()},
+ // Test for URL with non-existing filesystem id.
+ {"invalid", "external", false /* is_valid */,
+ // The rest of values will be ignored.
+ storage::kFileSystemTypeUnknown, storage::kFileSystemTypeUnknown,
+ FPL(""), std::string()},
+ };
+
+ for (size_t i = 0; i < arraysize(kTestCases); ++i) {
+ const base::FilePath virtual_path =
+ base::FilePath::FromUTF8Unsafe(
+ kTestCases[i].root).Append(kVirtualPathNoRoot);
+
+ GURL raw_url =
+ CreateRawFileSystemURL(kTestCases[i].type_str, kTestCases[i].root);
+ FileSystemURL cracked_url = file_system_context->CrackURL(raw_url);
+
+ SCOPED_TRACE(testing::Message() << "Test case " << i << ": "
+ << "Cracking URL: " << raw_url);
+
+ EXPECT_EQ(kTestCases[i].expect_is_valid, cracked_url.is_valid());
+ if (!kTestCases[i].expect_is_valid)
+ continue;
+
+ ExpectFileSystemURLMatches(
+ cracked_url,
+ GURL(kTestOrigin),
+ kTestCases[i].expect_mount_type,
+ kTestCases[i].expect_type,
+ base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(),
+ virtual_path.NormalizePathSeparators(),
+ kTestCases[i].expect_filesystem_id);
+ }
+
+ IsolatedContext::GetInstance()->RevokeFileSystemByPath(
+ base::FilePath(DRIVE FPL("/test/isolated/root")));
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("ext");
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kIsolatedFileSystemID);
+}
+
+TEST_F(FileSystemContextTest, CanServeURLRequest) {
+ scoped_refptr<ExternalMountPoints> external_mount_points(
+ ExternalMountPoints::CreateRefCounted());
+ scoped_refptr<FileSystemContext> context(
+ CreateFileSystemContextForTest(external_mount_points.get()));
+
+ // A request for a sandbox mount point should be served.
+ FileSystemURL cracked_url =
+ context->CrackURL(CreateRawFileSystemURL("persistent", "pers_mount"));
+ EXPECT_EQ(storage::kFileSystemTypePersistent, cracked_url.mount_type());
+ EXPECT_TRUE(context->CanServeURLRequest(cracked_url));
+
+ // A request for an isolated mount point should NOT be served.
+ std::string isolated_fs_name = "root";
+ std::string isolated_fs_id =
+ IsolatedContext::GetInstance()->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal,
+ std::string(),
+ base::FilePath(DRIVE FPL("/test/isolated/root")),
+ &isolated_fs_name);
+ cracked_url = context->CrackURL(
+ CreateRawFileSystemURL("isolated", isolated_fs_id));
+ EXPECT_EQ(storage::kFileSystemTypeIsolated, cracked_url.mount_type());
+ EXPECT_FALSE(context->CanServeURLRequest(cracked_url));
+
+ // A request for an external mount point should be served.
+ const std::string kExternalMountName = "ext_mount";
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ kExternalMountName,
+ storage::kFileSystemTypeDrive,
+ FileSystemMountOption(),
+ base::FilePath()));
+ cracked_url = context->CrackURL(
+ CreateRawFileSystemURL("external", kExternalMountName));
+ EXPECT_EQ(storage::kFileSystemTypeExternal, cracked_url.mount_type());
+ EXPECT_TRUE(context->CanServeURLRequest(cracked_url));
+
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kExternalMountName);
+ IsolatedContext::GetInstance()->RevokeFileSystem(isolated_fs_id);
+}
+
+// Ensures that a backend exists for each common isolated file system type.
+// See http://crbug.com/447027
+TEST_F(FileSystemContextTest, IsolatedFileSystemsTypesHandled) {
+ // This does not provide any "additional" file system handlers. In particular,
+ // on Chrome OS it does not provide chromeos::FileSystemBackend.
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(nullptr));
+
+ // Isolated file system types are handled.
+ EXPECT_TRUE(file_system_context->GetFileSystemBackend(
+ storage::kFileSystemTypeIsolated));
+ EXPECT_TRUE(file_system_context->GetFileSystemBackend(
+ storage::kFileSystemTypeDragged));
+ EXPECT_TRUE(file_system_context->GetFileSystemBackend(
+ storage::kFileSystemTypeForTransientFile));
+ EXPECT_TRUE(file_system_context->GetFileSystemBackend(
+ storage::kFileSystemTypeNativeLocal));
+ EXPECT_TRUE(file_system_context->GetFileSystemBackend(
+ storage::kFileSystemTypeNativeForPlatformApp));
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_dir_url_request_job_unittest.cc b/chromium/storage/browser/fileapi/file_system_dir_url_request_job_unittest.cc
new file mode 100644
index 00000000000..ed573096fc7
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_dir_url_request_job_unittest.cc
@@ -0,0 +1,459 @@
+// Copyright 2013 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 <stdint.h>
+#include <string>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_request_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_dir_url_request_job.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/test_file_system_backend.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+
+using storage::FileSystemContext;
+using storage::FileSystemOperationContext;
+using storage::FileSystemURL;
+
+namespace content {
+namespace {
+
+// We always use the TEMPORARY FileSystem in this test.
+const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
+
+const char kValidExternalMountPoint[] = "mnt_name";
+
+// An auto mounter that will try to mount anything for |storage_domain| =
+// "automount", but will only succeed for the mount point "mnt_name".
+bool TestAutoMountForURLRequest(
+ const net::URLRequest* /*url_request*/,
+ const storage::FileSystemURL& filesystem_url,
+ const std::string& storage_domain,
+ const base::Callback<void(base::File::Error result)>& callback) {
+ if (storage_domain != "automount")
+ return false;
+
+ std::vector<base::FilePath::StringType> components;
+ filesystem_url.path().GetComponents(&components);
+ std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe();
+
+ if (mount_point == kValidExternalMountPoint) {
+ storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ kValidExternalMountPoint,
+ storage::kFileSystemTypeTest,
+ storage::FileSystemMountOption(),
+ base::FilePath());
+ callback.Run(base::File::FILE_OK);
+ } else {
+ callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+ }
+ return true;
+}
+
+class FileSystemDirURLRequestJobFactory : public net::URLRequestJobFactory {
+ public:
+ FileSystemDirURLRequestJobFactory(const std::string& storage_domain,
+ FileSystemContext* context)
+ : storage_domain_(storage_domain), file_system_context_(context) {
+ }
+
+ net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return new storage::FileSystemDirURLRequestJob(
+ request, network_delegate, storage_domain_, file_system_context_);
+ }
+
+ net::URLRequestJob* MaybeInterceptRedirect(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const GURL& location) const override {
+ return nullptr;
+ }
+
+ net::URLRequestJob* MaybeInterceptResponse(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return nullptr;
+ }
+
+ bool IsHandledProtocol(const std::string& scheme) const override {
+ return true;
+ }
+
+ bool IsSafeRedirectTarget(const GURL& location) const override {
+ return false;
+ }
+
+ private:
+ std::string storage_domain_;
+ FileSystemContext* file_system_context_;
+};
+
+
+} // namespace
+
+class FileSystemDirURLRequestJobTest : public testing::Test {
+ protected:
+ FileSystemDirURLRequestJobTest()
+ : weak_factory_(this) {
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ special_storage_policy_ = new MockSpecialStoragePolicy;
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, temp_dir_.GetPath());
+
+ file_system_context_->OpenFileSystem(
+ GURL("http://remote/"),
+ storage::kFileSystemTypeTemporary,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem,
+ weak_factory_.GetWeakPtr()));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void TearDown() override {
+ // NOTE: order matters, request must die before delegate
+ request_.reset(NULL);
+ delegate_.reset(NULL);
+ }
+
+ void SetUpAutoMountContext(base::FilePath* mnt_point) {
+ *mnt_point = temp_dir_.GetPath().AppendASCII("auto_mount_dir");
+ ASSERT_TRUE(base::CreateDirectory(*mnt_point));
+
+ std::vector<std::unique_ptr<storage::FileSystemBackend>>
+ additional_providers;
+ additional_providers.push_back(base::MakeUnique<TestFileSystemBackend>(
+ base::ThreadTaskRunnerHandle::Get().get(), *mnt_point));
+
+ std::vector<storage::URLRequestAutoMountHandler> handlers;
+ handlers.push_back(base::Bind(&TestAutoMountForURLRequest));
+
+ file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting(
+ NULL, std::move(additional_providers), handlers, temp_dir_.GetPath());
+ }
+
+ void OnOpenFileSystem(const GURL& root_url,
+ const std::string& name,
+ base::File::Error result) {
+ ASSERT_EQ(base::File::FILE_OK, result);
+ }
+
+ void TestRequestHelper(const GURL& url, bool run_to_completion,
+ FileSystemContext* file_system_context) {
+ delegate_.reset(new net::TestDelegate());
+ delegate_->set_quit_on_redirect(true);
+ job_factory_.reset(new FileSystemDirURLRequestJobFactory(
+ url.GetOrigin().host(), file_system_context));
+ empty_context_.set_job_factory(job_factory_.get());
+
+ request_ = empty_context_.CreateRequest(url, net::DEFAULT_PRIORITY,
+ delegate_.get(),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ request_->Start();
+ ASSERT_TRUE(request_->is_pending()); // verify that we're starting async
+ if (run_to_completion)
+ base::RunLoop().Run();
+ }
+
+ void TestRequest(const GURL& url) {
+ TestRequestHelper(url, true, file_system_context_.get());
+ }
+
+ void TestRequestWithContext(const GURL& url,
+ FileSystemContext* file_system_context) {
+ TestRequestHelper(url, true, file_system_context);
+ }
+
+ void TestRequestNoRun(const GURL& url) {
+ TestRequestHelper(url, false, file_system_context_.get());
+ }
+
+ FileSystemURL CreateURL(const base::FilePath& file_path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"), storage::kFileSystemTypeTemporary, file_path);
+ }
+
+ FileSystemOperationContext* NewOperationContext() {
+ FileSystemOperationContext* context(
+ new FileSystemOperationContext(file_system_context_.get()));
+ context->set_allowed_bytes_growth(1024);
+ return context;
+ }
+
+ void CreateDirectory(const base::StringPiece& dir_name) {
+ base::FilePath path = base::FilePath().AppendASCII(dir_name);
+ std::unique_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::File::FILE_OK, file_util()->CreateDirectory(
+ context.get(),
+ CreateURL(path),
+ false /* exclusive */,
+ false /* recursive */));
+ }
+
+ void EnsureFileExists(const base::StringPiece file_name) {
+ base::FilePath path = base::FilePath().AppendASCII(file_name);
+ std::unique_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::File::FILE_OK, file_util()->EnsureFileExists(
+ context.get(), CreateURL(path), NULL));
+ }
+
+ void TruncateFile(const base::StringPiece file_name, int64_t length) {
+ base::FilePath path = base::FilePath().AppendASCII(file_name);
+ std::unique_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::File::FILE_OK, file_util()->Truncate(
+ context.get(), CreateURL(path), length));
+ }
+
+ base::File::Error GetFileInfo(const base::FilePath& path,
+ base::File::Info* file_info,
+ base::FilePath* platform_file_path) {
+ std::unique_ptr<FileSystemOperationContext> context(NewOperationContext());
+ return file_util()->GetFileInfo(context.get(),
+ CreateURL(path),
+ file_info, platform_file_path);
+ }
+
+ // If |size| is negative, the reported size is ignored.
+ void VerifyListingEntry(const std::string& entry_line,
+ const std::string& name,
+ const std::string& url,
+ bool is_directory,
+ int64_t size) {
+#define NUMBER "([0-9-]*)"
+#define STR "([^\"]*)"
+ icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR
+ "\",(0|1)," NUMBER ",\"" STR "\"," NUMBER ",\"" STR "\"\\);</script>");
+#undef NUMBER
+#undef STR
+ icu::UnicodeString input(entry_line.c_str());
+
+ UErrorCode status = U_ZERO_ERROR;
+ icu::RegexMatcher match(pattern, input, 0, status);
+
+ EXPECT_TRUE(match.find());
+ EXPECT_EQ(7, match.groupCount());
+ EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status));
+ EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status));
+ EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"),
+ match.group(3, status));
+ if (size >= 0) {
+ icu::UnicodeString size_string(
+ base::FormatBytesUnlocalized(size).c_str());
+ EXPECT_EQ(size_string, match.group(5, status));
+ }
+
+ icu::UnicodeString date_ustr(match.group(7, status));
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateTimeInstance(icu::DateFormat::kShort));
+ UErrorCode parse_status = U_ZERO_ERROR;
+ UDate udate = formatter->parse(date_ustr, parse_status);
+ EXPECT_TRUE(U_SUCCESS(parse_status));
+ base::Time date = base::Time::FromJsTime(udate);
+ EXPECT_FALSE(date.is_null());
+ }
+
+ GURL CreateFileSystemURL(const std::string& path) {
+ return GURL(kFileSystemURLPrefix + path);
+ }
+
+ storage::FileSystemFileUtil* file_util() {
+ return file_system_context_->sandbox_delegate()->sync_file_util();
+ }
+
+ // Put the message loop at the top, so that it's the last thing deleted.
+ // Delete all task runner objects before the MessageLoop, to help prevent
+ // leaks caused by tasks posted during shutdown.
+ base::MessageLoopForIO message_loop_;
+
+ base::ScopedTempDir temp_dir_;
+ net::URLRequestContext empty_context_;
+ std::unique_ptr<net::TestDelegate> delegate_;
+ std::unique_ptr<net::URLRequest> request_;
+ std::unique_ptr<FileSystemDirURLRequestJobFactory> job_factory_;
+ scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_;
+};
+
+namespace {
+
+TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) {
+ CreateDirectory("foo");
+ CreateDirectory("foo/bar");
+ CreateDirectory("foo/bar/baz");
+
+ EnsureFileExists("foo/bar/hoge");
+ TruncateFile("foo/bar/hoge", 10);
+
+ TestRequest(CreateFileSystemURL("foo/bar/"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_GT(delegate_->bytes_received(), 0);
+
+ std::istringstream in(delegate_->data_received());
+ std::string line;
+ EXPECT_TRUE(std::getline(in, line));
+
+#if defined(OS_WIN)
+ EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line);
+#elif defined(OS_POSIX)
+ EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line);
+#endif
+
+ EXPECT_TRUE(std::getline(in, line));
+ VerifyListingEntry(line, "hoge", "hoge", false, 10);
+
+ EXPECT_TRUE(std::getline(in, line));
+ VerifyListingEntry(line, "baz", "baz", true, 0);
+ EXPECT_FALSE(!!std::getline(in, line));
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) {
+ TestRequest(GURL("filesystem:/foo/bar/baz"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_INVALID_URL, delegate_->request_status());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) {
+ TestRequest(GURL("filesystem:http://remote/persistent/somedir/"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) {
+ TestRequest(CreateFileSystemURL("somedir/"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, Cancel) {
+ CreateDirectory("foo");
+ TestRequestNoRun(CreateFileSystemURL("foo/"));
+ // Run StartAsync() and only StartAsync().
+ base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
+ request_.release());
+ base::RunLoop().RunUntilIdle();
+ // If we get here, success! we didn't crash!
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, Incognito) {
+ CreateDirectory("foo");
+
+ scoped_refptr<FileSystemContext> file_system_context =
+ CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.GetPath());
+
+ TestRequestWithContext(CreateFileSystemURL("/"),
+ file_system_context.get());
+ ASSERT_FALSE(request_->is_pending());
+
+ std::istringstream in(delegate_->data_received());
+ std::string line;
+ EXPECT_TRUE(std::getline(in, line));
+ EXPECT_FALSE(!!std::getline(in, line));
+
+ TestRequestWithContext(CreateFileSystemURL("foo"),
+ file_system_context.get());
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, AutoMountDirectoryListing) {
+ base::FilePath mnt_point;
+ SetUpAutoMountContext(&mnt_point);
+ ASSERT_TRUE(base::CreateDirectory(mnt_point));
+ ASSERT_TRUE(base::CreateDirectory(mnt_point.AppendASCII("foo")));
+ ASSERT_EQ(10,
+ base::WriteFile(mnt_point.AppendASCII("bar"), "1234567890", 10));
+
+ TestRequest(GURL("filesystem:http://automount/external/mnt_name"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_GT(delegate_->bytes_received(), 0);
+
+ std::istringstream in(delegate_->data_received());
+ std::string line;
+ EXPECT_TRUE(std::getline(in, line)); // |line| contains the temp dir path.
+
+ // Result order is not guaranteed, so sort the results.
+ std::vector<std::string> listing_entries;
+ while (!!std::getline(in, line))
+ listing_entries.push_back(line);
+
+ ASSERT_EQ(2U, listing_entries.size());
+ std::sort(listing_entries.begin(), listing_entries.end());
+ VerifyListingEntry(listing_entries[0], "bar", "bar", false, 10);
+ VerifyListingEntry(listing_entries[1], "foo", "foo", true, -1);
+
+ ASSERT_TRUE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kValidExternalMountPoint));
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, AutoMountInvalidRoot) {
+ base::FilePath mnt_point;
+ SetUpAutoMountContext(&mnt_point);
+ TestRequest(GURL("filesystem:http://automount/external/invalid"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+
+ ASSERT_FALSE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ "invalid"));
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, AutoMountNoHandler) {
+ base::FilePath mnt_point;
+ SetUpAutoMountContext(&mnt_point);
+ TestRequest(GURL("filesystem:http://noauto/external/mnt_name"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+
+ ASSERT_FALSE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kValidExternalMountPoint));
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc b/chromium/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc
new file mode 100644
index 00000000000..e12134fe291
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc
@@ -0,0 +1,275 @@
+// Copyright 2013 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 "storage/browser/fileapi/file_system_file_stream_reader.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemContext;
+using storage::FileSystemFileStreamReader;
+using storage::FileSystemType;
+using storage::FileSystemURL;
+
+namespace content {
+
+namespace {
+
+const char kURLOrigin[] = "http://remote/";
+const char kTestFileName[] = "test.dat";
+const char kTestData[] = "0123456789";
+const int kTestDataSize = arraysize(kTestData) - 1;
+
+void ReadFromReader(storage::FileSystemFileStreamReader* reader,
+ std::string* data,
+ size_t size,
+ int* result) {
+ ASSERT_TRUE(reader != NULL);
+ ASSERT_TRUE(result != NULL);
+ *result = net::OK;
+ net::TestCompletionCallback callback;
+ size_t total_bytes_read = 0;
+ while (total_bytes_read < size) {
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(size - total_bytes_read));
+ int rv = reader->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv < 0)
+ *result = rv;
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data->append(buf->data(), rv);
+ }
+}
+
+void NeverCalled(int unused) { ADD_FAILURE(); }
+
+} // namespace
+
+class FileSystemFileStreamReaderTest : public testing::Test {
+ public:
+ FileSystemFileStreamReaderTest() {}
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, temp_dir_.GetPath());
+
+ file_system_context_->OpenFileSystem(
+ GURL(kURLOrigin),
+ storage::kFileSystemTypeTemporary,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&OnOpenFileSystem));
+ base::RunLoop().RunUntilIdle();
+
+ WriteFile(kTestFileName, kTestData, kTestDataSize,
+ &test_file_modification_time_);
+ }
+
+ void TearDown() override { base::RunLoop().RunUntilIdle(); }
+
+ protected:
+ storage::FileSystemFileStreamReader* CreateFileReader(
+ const std::string& file_name,
+ int64_t initial_offset,
+ const base::Time& expected_modification_time) {
+ return new FileSystemFileStreamReader(file_system_context_.get(),
+ GetFileSystemURL(file_name),
+ initial_offset,
+ expected_modification_time);
+ }
+
+ base::Time test_file_modification_time() const {
+ return test_file_modification_time_;
+ }
+
+ void WriteFile(const std::string& file_name,
+ const char* buf,
+ int buf_size,
+ base::Time* modification_time) {
+ FileSystemURL url = GetFileSystemURL(file_name);
+
+ ASSERT_EQ(base::File::FILE_OK,
+ content::AsyncFileTestHelper::CreateFileWithData(
+ file_system_context_.get(), url, buf, buf_size));
+
+ base::File::Info file_info;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context_.get(), url, &file_info));
+ if (modification_time)
+ *modification_time = file_info.last_modified;
+ }
+
+ private:
+ static void OnOpenFileSystem(const GURL& root_url,
+ const std::string& name,
+ base::File::Error result) {
+ ASSERT_EQ(base::File::FILE_OK, result);
+ }
+
+ FileSystemURL GetFileSystemURL(const std::string& file_name) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL(kURLOrigin),
+ storage::kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(file_name));
+ }
+
+ base::MessageLoopForIO message_loop_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::Time test_file_modification_time_;
+};
+
+TEST_F(FileSystemFileStreamReaderTest, NonExistent) {
+ const char kFileName[] = "nonexistent";
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kFileName, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
+ ASSERT_EQ(0U, data.size());
+}
+
+TEST_F(FileSystemFileStreamReaderTest, Empty) {
+ const char kFileName[] = "empty";
+ WriteFile(kFileName, NULL, 0, NULL);
+
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kFileName, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(0U, data.size());
+
+ net::TestInt64CompletionCallback callback;
+ int64_t length_result = reader->GetLength(callback.callback());
+ if (length_result == net::ERR_IO_PENDING)
+ length_result = callback.WaitForResult();
+ ASSERT_EQ(0, length_result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) {
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, test_file_modification_time()));
+ net::TestInt64CompletionCallback callback;
+ int64_t result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) {
+ // Pass a fake expected modifictaion time so that the expectation fails.
+ base::Time fake_expected_modification_time =
+ test_file_modification_time() - base::TimeDelta::FromSeconds(10);
+
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
+ net::TestInt64CompletionCallback callback;
+ int64_t result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+
+ // With NULL expected modification time this should work.
+ reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
+ result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) {
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 3, base::Time()));
+ net::TestInt64CompletionCallback callback;
+ int64_t result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ // Initial offset does not affect the result of GetLength.
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadNormal) {
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, test_file_modification_time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) {
+ // Pass a fake expected modifictaion time so that the expectation fails.
+ base::Time fake_expected_modification_time =
+ test_file_modification_time() - base::TimeDelta::FromSeconds(10);
+
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+ ASSERT_EQ(0U, data.size());
+
+ // With NULL expected modification time this should work.
+ data.clear();
+ reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) {
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 3, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(&kTestData[3], data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) {
+ std::unique_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, base::Time()));
+
+ net::TestCompletionCallback callback;
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(kTestDataSize));
+ int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled));
+ ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0);
+
+ // Delete immediately.
+ // Should not crash; nor should NeverCalled be callback.
+ reader.reset();
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_operation_impl_unittest.cc b/chromium/storage/browser/fileapi/file_system_operation_impl_unittest.cc
new file mode 100644
index 00000000000..ed6f701ff07
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_operation_impl_unittest.cc
@@ -0,0 +1,1313 @@
+// Copyright 2013 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 "storage/browser/fileapi/file_system_operation_impl.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/blob/shareable_file_reference.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/mock_file_change_observer.h"
+#include "storage/browser/test/mock_file_update_observer.h"
+#include "storage/browser/test/mock_quota_manager.h"
+#include "storage/browser/test/mock_quota_manager_proxy.h"
+#include "storage/browser/test/sandbox_file_system_test_helper.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemOperation;
+using storage::FileSystemOperationContext;
+using storage::FileSystemOperationRunner;
+using storage::FileSystemURL;
+using storage::QuotaManager;
+using storage::QuotaManagerProxy;
+using storage::ShareableFileReference;
+
+namespace content {
+
+// Test class for FileSystemOperationImpl.
+class FileSystemOperationImplTest
+ : public testing::Test {
+ public:
+ FileSystemOperationImplTest() : weak_factory_(this) {}
+
+ protected:
+ void SetUp() override {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ change_observers_ =
+ storage::MockFileChangeObserver::CreateList(&change_observer_);
+ update_observers_ =
+ storage::MockFileUpdateObserver::CreateList(&update_observer_);
+
+ base::FilePath base_dir = base_.GetPath().AppendASCII("filesystem");
+ quota_manager_ =
+ new MockQuotaManager(false /* is_incognito */, base_dir,
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ NULL /* special storage policy */);
+ quota_manager_proxy_ = new MockQuotaManagerProxy(
+ quota_manager(), base::ThreadTaskRunnerHandle::Get().get());
+ sandbox_file_system_.SetUp(base_dir, quota_manager_proxy_.get());
+ sandbox_file_system_.AddFileChangeObserver(&change_observer_);
+ sandbox_file_system_.AddFileUpdateObserver(&update_observer_);
+ update_observer_.Disable();
+ }
+
+ void TearDown() override {
+ // Let the client go away before dropping a ref of the quota manager proxy.
+ quota_manager_proxy()->SimulateQuotaManagerDestroyed();
+ quota_manager_ = NULL;
+ quota_manager_proxy_ = NULL;
+ sandbox_file_system_.TearDown();
+ }
+
+ FileSystemOperationRunner* operation_runner() {
+ return sandbox_file_system_.operation_runner();
+ }
+
+ const base::File::Info& info() const { return info_; }
+ const base::FilePath& path() const { return path_; }
+ const std::vector<storage::DirectoryEntry>& entries() const {
+ return entries_;
+ }
+
+ const ShareableFileReference* shareable_file_ref() const {
+ return shareable_file_ref_.get();
+ }
+
+ MockQuotaManager* quota_manager() {
+ return static_cast<MockQuotaManager*>(quota_manager_.get());
+ }
+
+ MockQuotaManagerProxy* quota_manager_proxy() {
+ return static_cast<MockQuotaManagerProxy*>(
+ quota_manager_proxy_.get());
+ }
+
+ storage::FileSystemFileUtil* file_util() {
+ return sandbox_file_system_.file_util();
+ }
+
+ storage::MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ std::unique_ptr<FileSystemOperationContext> NewContext() {
+ FileSystemOperationContext* context =
+ sandbox_file_system_.NewOperationContext();
+ // Grant enough quota for all test cases.
+ context->set_allowed_bytes_growth(1000000);
+ return base::WrapUnique(context);
+ }
+
+ FileSystemURL URLForPath(const std::string& path) const {
+ return sandbox_file_system_.CreateURLFromUTF8(path);
+ }
+
+ base::FilePath PlatformPath(const std::string& path) {
+ return sandbox_file_system_.GetLocalPath(
+ base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ bool FileExists(const std::string& path) {
+ return AsyncFileTestHelper::FileExists(
+ sandbox_file_system_.file_system_context(), URLForPath(path),
+ AsyncFileTestHelper::kDontCheckSize);
+ }
+
+ bool DirectoryExists(const std::string& path) {
+ return AsyncFileTestHelper::DirectoryExists(
+ sandbox_file_system_.file_system_context(), URLForPath(path));
+ }
+
+ FileSystemURL CreateFile(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ bool created = false;
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->EnsureFileExists(NewContext().get(),
+ url, &created));
+ EXPECT_TRUE(created);
+ return url;
+ }
+
+ FileSystemURL CreateDirectory(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->CreateDirectory(NewContext().get(), url,
+ false /* exclusive */, true));
+ return url;
+ }
+
+ int64_t GetFileSize(const std::string& path) {
+ base::File::Info info;
+ EXPECT_TRUE(base::GetFileInfo(PlatformPath(path), &info));
+ return info.size;
+ }
+
+ // Callbacks for recording test results.
+ FileSystemOperation::StatusCallback RecordStatusCallback(
+ const base::Closure& closure,
+ base::File::Error* status) {
+ return base::Bind(&FileSystemOperationImplTest::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ closure,
+ status);
+ }
+
+ FileSystemOperation::ReadDirectoryCallback RecordReadDirectoryCallback(
+ const base::Closure& closure,
+ base::File::Error* status) {
+ return base::Bind(&FileSystemOperationImplTest::DidReadDirectory,
+ weak_factory_.GetWeakPtr(),
+ closure,
+ status);
+ }
+
+ FileSystemOperation::GetMetadataCallback RecordMetadataCallback(
+ const base::Closure& closure,
+ base::File::Error* status) {
+ return base::Bind(&FileSystemOperationImplTest::DidGetMetadata,
+ weak_factory_.GetWeakPtr(),
+ closure,
+ status);
+ }
+
+ FileSystemOperation::SnapshotFileCallback RecordSnapshotFileCallback(
+ const base::Closure& closure,
+ base::File::Error* status) {
+ return base::Bind(&FileSystemOperationImplTest::DidCreateSnapshotFile,
+ weak_factory_.GetWeakPtr(),
+ closure,
+ status);
+ }
+
+ void DidFinish(const base::Closure& closure,
+ base::File::Error* status,
+ base::File::Error actual) {
+ *status = actual;
+ closure.Run();
+ }
+
+ void DidReadDirectory(const base::Closure& closure,
+ base::File::Error* status,
+ base::File::Error actual,
+ const std::vector<storage::DirectoryEntry>& entries,
+ bool /* has_more */) {
+ entries_ = entries;
+ *status = actual;
+ closure.Run();
+ }
+
+ void DidGetMetadata(const base::Closure& closure,
+ base::File::Error* status,
+ base::File::Error actual,
+ const base::File::Info& info) {
+ info_ = info;
+ *status = actual;
+ closure.Run();
+ }
+
+ void DidCreateSnapshotFile(
+ const base::Closure& closure,
+ base::File::Error* status,
+ base::File::Error actual,
+ const base::File::Info& info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<ShareableFileReference>& shareable_file_ref) {
+ info_ = info;
+ path_ = platform_path;
+ *status = actual;
+ shareable_file_ref_ = shareable_file_ref;
+ closure.Run();
+ }
+
+ int64_t GetDataSizeOnDisk() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ void GetUsageAndQuota(int64_t* usage, int64_t* quota) {
+ storage::QuotaStatusCode status =
+ AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
+ sandbox_file_system_.origin(),
+ sandbox_file_system_.type(),
+ usage,
+ quota);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(storage::kQuotaStatusOk, status);
+ }
+
+ int64_t ComputePathCost(const FileSystemURL& url) {
+ int64_t base_usage;
+ GetUsageAndQuota(&base_usage, NULL);
+
+ AsyncFileTestHelper::CreateFile(
+ sandbox_file_system_.file_system_context(), url);
+ EXPECT_EQ(base::File::FILE_OK, Remove(url, false /* recursive */));
+
+ change_observer()->ResetCount();
+
+ int64_t total_usage;
+ GetUsageAndQuota(&total_usage, NULL);
+ return total_usage - base_usage;
+ }
+
+ void GrantQuotaForCurrentUsage() {
+ int64_t usage;
+ GetUsageAndQuota(&usage, NULL);
+ quota_manager()->SetQuota(sandbox_file_system_.origin(),
+ sandbox_file_system_.storage_type(),
+ usage);
+ }
+
+ int64_t GetUsage() {
+ int64_t usage = 0;
+ GetUsageAndQuota(&usage, NULL);
+ return usage;
+ }
+
+ void AddQuota(int64_t quota_delta) {
+ int64_t quota;
+ GetUsageAndQuota(NULL, &quota);
+ quota_manager()->SetQuota(sandbox_file_system_.origin(),
+ sandbox_file_system_.storage_type(),
+ quota + quota_delta);
+ }
+
+ base::File::Error Move(
+ const FileSystemURL& src,
+ const FileSystemURL& dest,
+ storage::FileSystemOperation::CopyOrMoveOption option) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->Move(
+ src,
+ dest,
+ option,
+ RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error Copy(
+ const FileSystemURL& src,
+ const FileSystemURL& dest,
+ storage::FileSystemOperation::CopyOrMoveOption option) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->Copy(
+ src, dest, option, storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
+ FileSystemOperationRunner::CopyProgressCallback(),
+ RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error CopyInForeignFile(const base::FilePath& src,
+ const FileSystemURL& dest) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->CopyInForeignFile(
+ src, dest, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error Truncate(const FileSystemURL& url, int size) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->Truncate(
+ url, size, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error CreateFile(const FileSystemURL& url, bool exclusive) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->CreateFile(
+ url, exclusive, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error Remove(const FileSystemURL& url, bool recursive) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->Remove(
+ url, recursive, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error CreateDirectory(const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->CreateDirectory(
+ url,
+ exclusive,
+ recursive,
+ RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error GetMetadata(const FileSystemURL& url, int fields) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->GetMetadata(
+ url, fields, RecordMetadataCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error ReadDirectory(const FileSystemURL& url) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->ReadDirectory(
+ url, RecordReadDirectoryCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error CreateSnapshotFile(const FileSystemURL& url) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->CreateSnapshotFile(
+ url, RecordSnapshotFileCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error FileExists(const FileSystemURL& url) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->FileExists(
+ url, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error DirectoryExists(const FileSystemURL& url) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->DirectoryExists(
+ url, RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ base::File::Error TouchFile(const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ base::File::Error status;
+ base::RunLoop run_loop;
+ update_observer_.Enable();
+ operation_runner()->TouchFile(
+ url,
+ last_access_time,
+ last_modified_time,
+ RecordStatusCallback(run_loop.QuitClosure(), &status));
+ run_loop.Run();
+ update_observer_.Disable();
+ return status;
+ }
+
+ private:
+ base::MessageLoopForIO message_loop_;
+ scoped_refptr<QuotaManager> quota_manager_;
+ scoped_refptr<QuotaManagerProxy> quota_manager_proxy_;
+
+ // Common temp base for nondestructive uses.
+ base::ScopedTempDir base_;
+
+ SandboxFileSystemTestHelper sandbox_file_system_;
+
+ // For post-operation status.
+ base::File::Info info_;
+ base::FilePath path_;
+ std::vector<storage::DirectoryEntry> entries_;
+ scoped_refptr<ShareableFileReference> shareable_file_ref_;
+
+ storage::MockFileChangeObserver change_observer_;
+ storage::ChangeObserverList change_observers_;
+ storage::MockFileUpdateObserver update_observer_;
+ storage::UpdateObserverList update_observers_;
+
+ base::WeakPtrFactory<FileSystemOperationImplTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplTest);
+};
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDoesntExist) {
+ change_observer()->ResetCount();
+ EXPECT_EQ(
+ base::File::FILE_ERROR_NOT_FOUND,
+ Move(URLForPath("a"), URLForPath("b"), FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureContainsPath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("src/dest"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Move(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDirExistsDestFile) {
+ // Src exists and is dir. Dest is a file.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Move(src_dir, dest_file, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestMoveFailureSrcFileExistsDestNonEmptyDir) {
+ // Src exists and is a directory. Dest is a non-empty directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
+ Move(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcFileExistsDestDir) {
+ // Src exists and is a file. Dest is a directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL src_file(CreateFile("src/file"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Move(src_file, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ Move(src_dir,
+ URLForPath("nonexistent/deset"),
+ FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndOverwrite) {
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_file(CreateFile("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Move(src_file, dest_file, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(FileExists("dest"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndNew) {
+ FileSystemURL src_file(CreateFile("src"));
+
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ Move(src_file, URLForPath("new"), FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(FileExists("new"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndOverwrite) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Move(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_FALSE(DirectoryExists("src"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Make sure we've overwritten but not moved the source under the |dest_dir|.
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_FALSE(DirectoryExists("dest/src"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndNew) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ Move(src_dir, URLForPath("dest/new"), FileSystemOperation::OPTION_NONE));
+ EXPECT_FALSE(DirectoryExists("src"));
+ EXPECT_TRUE(DirectoryExists("dest/new"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirRecursive) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Move(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/sub"));
+
+ EXPECT_EQ(3, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSamePath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Move(src_dir, src_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(DirectoryExists("src/dir"));
+ EXPECT_TRUE(FileExists("src/dir/sub"));
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(0, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(0, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_EQ(0, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDoesntExist) {
+ EXPECT_EQ(
+ base::File::FILE_ERROR_NOT_FOUND,
+ Copy(URLForPath("a"), URLForPath("b"), FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureContainsPath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("src/dir"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Copy(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDirExistsDestFile) {
+ // Src exists and is dir. Dest is a file.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Copy(src_dir, dest_file, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCopyFailureSrcFileExistsDestNonEmptyDir) {
+ // Src exists and is a directory. Dest is a non-empty directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
+ Copy(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcFileExistsDestDir) {
+ // Src exists and is a file. Dest is a directory.
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
+ Copy(src_file, dest_dir, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ FileSystemURL src_dir(CreateDirectory("src"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ Copy(src_dir,
+ URLForPath("nonexistent/dest"),
+ FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureByQuota) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL src_file(CreateFile("src/file"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(src_file, 6));
+ EXPECT_EQ(6, GetFileSize("src/file"));
+
+ FileSystemURL dest_file(URLForPath("dest/file"));
+ int64_t dest_path_cost = ComputePathCost(dest_file);
+ GrantQuotaForCurrentUsage();
+ AddQuota(6 + dest_path_cost - 1);
+
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ Copy(src_file, dest_file, FileSystemOperation::OPTION_NONE));
+ EXPECT_FALSE(FileExists("dest/file"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndOverwrite) {
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_file(CreateFile("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_file, dest_file, FileSystemOperation::OPTION_NONE));
+
+ EXPECT_TRUE(FileExists("dest"));
+ EXPECT_EQ(4, quota_manager_proxy()->notify_storage_accessed_count());
+ EXPECT_EQ(2, change_observer()->get_and_reset_modify_file_count());
+
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndNew) {
+ FileSystemURL src_file(CreateFile("src"));
+
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ Copy(src_file, URLForPath("new"), FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(FileExists("new"));
+ EXPECT_EQ(4, quota_manager_proxy()->notify_storage_accessed_count());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndOverwrite) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+
+ // Make sure we've overwritten but not copied the source under the |dest_dir|.
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_FALSE(DirectoryExists("dest/src"));
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 3);
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndNew) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir_new(URLForPath("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_dir, dest_dir_new, FileSystemOperation::OPTION_NONE));
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 2);
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirRecursive) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_dir, dest_dir, FileSystemOperation::OPTION_NONE));
+
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/sub"));
+
+ // For recursive copy we may record multiple read access.
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 1);
+
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSamePath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_dir, src_dir, FileSystemOperation::OPTION_NONE));
+
+ EXPECT_TRUE(DirectoryExists("src/dir"));
+ EXPECT_TRUE(FileExists("src/dir/sub"));
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(0, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_EQ(0, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileSuccess) {
+ base::FilePath src_local_disk_file_path;
+ base::CreateTemporaryFile(&src_local_disk_file_path);
+ const char test_data[] = "foo";
+ int data_size = arraysize(test_data);
+ base::WriteFile(src_local_disk_file_path, test_data, data_size);
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ int64_t before_usage;
+ GetUsageAndQuota(&before_usage, NULL);
+
+ // Check that the file copied and corresponding usage increased.
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ CopyInForeignFile(src_local_disk_file_path, URLForPath("dest/file")));
+
+ EXPECT_EQ(1, change_observer()->create_file_count());
+ EXPECT_TRUE(FileExists("dest/file"));
+ int64_t after_usage;
+ GetUsageAndQuota(&after_usage, NULL);
+ EXPECT_GT(after_usage, before_usage);
+
+ // Compare contents of src and copied file.
+ char buffer[100];
+ EXPECT_EQ(data_size, base::ReadFile(PlatformPath("dest/file"),
+ buffer, data_size));
+ for (int i = 0; i < data_size; ++i)
+ EXPECT_EQ(test_data[i], buffer[i]);
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileFailureByQuota) {
+ base::FilePath src_local_disk_file_path;
+ base::CreateTemporaryFile(&src_local_disk_file_path);
+ const char test_data[] = "foo";
+ base::WriteFile(src_local_disk_file_path, test_data, arraysize(test_data));
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ GrantQuotaForCurrentUsage();
+ EXPECT_EQ(
+ base::File::FILE_ERROR_NO_SPACE,
+ CopyInForeignFile(src_local_disk_file_path, URLForPath("dest/file")));
+
+ EXPECT_FALSE(FileExists("dest/file"));
+ EXPECT_EQ(0, change_observer()->create_file_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileFailure) {
+ // Already existing file and exclusive true.
+ FileSystemURL file(CreateFile("file"));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, CreateFile(file, true));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileExists) {
+ // Already existing file and exclusive false.
+ FileSystemURL file(CreateFile("file"));
+ EXPECT_EQ(base::File::FILE_OK, CreateFile(file, false));
+ EXPECT_TRUE(FileExists("file"));
+
+ // The file was already there; did nothing.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessExclusive) {
+ // File doesn't exist but exclusive is true.
+ EXPECT_EQ(base::File::FILE_OK, CreateFile(URLForPath("new"), true));
+ EXPECT_TRUE(FileExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileDoesntExist) {
+ // Non existing file.
+ EXPECT_EQ(base::File::FILE_OK, CreateFile(URLForPath("nonexistent"), false));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCreateDirFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ CreateDirectory(URLForPath("nonexistent/dir"), false, false));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirFailureDirExists) {
+ // Exclusive and dir existing at path.
+ FileSystemURL dir(CreateDirectory("dir"));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, CreateDirectory(dir, true, false));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirFailureFileExists) {
+ // Exclusive true and file existing at path.
+ FileSystemURL file(CreateFile("file"));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, CreateDirectory(file, true, false));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirSuccess) {
+ // Dir exists and exclusive is false.
+ FileSystemURL dir(CreateDirectory("dir"));
+ EXPECT_EQ(base::File::FILE_OK, CreateDirectory(dir, false, false));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Dir doesn't exist.
+ EXPECT_EQ(base::File::FILE_OK,
+ CreateDirectory(URLForPath("new"), false, false));
+ EXPECT_TRUE(DirectoryExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirSuccessExclusive) {
+ // Dir doesn't exist.
+ EXPECT_EQ(base::File::FILE_OK,
+ CreateDirectory(URLForPath("new"), true, false));
+ EXPECT_TRUE(DirectoryExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataFailure) {
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ GetMetadata(URLForPath("nonexistent"),
+ storage::FileSystemOperation::GET_METADATA_FIELD_NONE));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ FileExists(URLForPath("nonexistent")));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ DirectoryExists(URLForPath("nonexistent")));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataSuccess) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ FileSystemURL file(CreateFile("dir/file"));
+ int read_access = 0;
+
+ EXPECT_EQ(base::File::FILE_OK, DirectoryExists(dir));
+ ++read_access;
+
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ GetMetadata(
+ dir, storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY));
+ EXPECT_TRUE(info().is_directory);
+ ++read_access;
+
+ EXPECT_EQ(base::File::FILE_OK, FileExists(file));
+ ++read_access;
+
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ GetMetadata(
+ file, storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY));
+ EXPECT_FALSE(info().is_directory);
+ ++read_access;
+
+ EXPECT_EQ(read_access,
+ quota_manager_proxy()->notify_storage_accessed_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTypeMismatchErrors) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE, FileExists(dir));
+
+ FileSystemURL file(CreateFile("file"));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY, DirectoryExists(file));
+}
+
+TEST_F(FileSystemOperationImplTest, TestReadDirFailure) {
+ // Path doesn't exist
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ReadDirectory(URLForPath("nonexistent")));
+
+ // File exists.
+ FileSystemURL file(CreateFile("file"));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY, ReadDirectory(file));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestReadDirSuccess) {
+ // parent_dir
+ // | |
+ // child_dir child_file
+ // Verify reading parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ FileSystemURL child_file(CreateFile("dir/child_file"));
+
+ EXPECT_EQ(base::File::FILE_OK, ReadDirectory(parent_dir));
+ EXPECT_EQ(2u, entries().size());
+
+ for (size_t i = 0; i < entries().size(); ++i) {
+ if (entries()[i].is_directory)
+ EXPECT_EQ(FILE_PATH_LITERAL("child_dir"), entries()[i].name);
+ else
+ EXPECT_EQ(FILE_PATH_LITERAL("child_file"), entries()[i].name);
+ }
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveFailure) {
+ // Path doesn't exist.
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ Remove(URLForPath("nonexistent"), false /* recursive */));
+
+ // It's an error to try to remove a non-empty directory if recursive flag
+ // is false.
+ // parent_dir
+ // | |
+ // child_dir child_file
+ // Verify deleting parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ FileSystemURL child_file(CreateFile("dir/child_file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
+ Remove(parent_dir, false /* recursive */));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveSuccess) {
+ FileSystemURL empty_dir(CreateDirectory("empty_dir"));
+ EXPECT_TRUE(DirectoryExists("empty_dir"));
+ EXPECT_EQ(base::File::FILE_OK, Remove(empty_dir, false /* recursive */));
+ EXPECT_FALSE(DirectoryExists("empty_dir"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveSuccessRecursive) {
+ // Removing a non-empty directory with recursive flag == true should be ok.
+ // parent_dir
+ // | |
+ // child_dir child_files
+ // |
+ // child_files
+ //
+ // Verify deleting parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ for (int i = 0; i < 8; ++i)
+ CreateFile(base::StringPrintf("dir/file-%d", i));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ for (int i = 0; i < 8; ++i)
+ CreateFile(base::StringPrintf("dir/child_dir/file-%d", i));
+
+ EXPECT_EQ(base::File::FILE_OK, Remove(parent_dir, true /* recursive */));
+ EXPECT_FALSE(DirectoryExists("parent_dir"));
+
+ EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(16, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTruncate) {
+ FileSystemURL file(CreateFile("file"));
+ base::FilePath platform_path = PlatformPath("file");
+
+ char test_data[] = "test data";
+ int data_size = static_cast<int>(sizeof(test_data));
+ EXPECT_EQ(data_size,
+ base::WriteFile(platform_path, test_data, data_size));
+
+ // Check that its length is the size of the data written.
+ EXPECT_EQ(
+ base::File::FILE_OK,
+ GetMetadata(
+ file, storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
+ storage::FileSystemOperation::GET_METADATA_FIELD_SIZE));
+ EXPECT_FALSE(info().is_directory);
+ EXPECT_EQ(data_size, info().size);
+
+ // Extend the file by truncating it.
+ int length = 17;
+ EXPECT_EQ(base::File::FILE_OK, Truncate(file, length));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Check that its length is now 17 and that it's all zeroes after the test
+ // data.
+ EXPECT_EQ(length, GetFileSize("file"));
+ char data[100];
+ EXPECT_EQ(length, base::ReadFile(platform_path, data, length));
+ for (int i = 0; i < length; ++i) {
+ if (i < static_cast<int>(sizeof(test_data)))
+ EXPECT_EQ(test_data[i], data[i]);
+ else
+ EXPECT_EQ(0, data[i]);
+ }
+
+ // Shorten the file by truncating it.
+ length = 3;
+ EXPECT_EQ(base::File::FILE_OK, Truncate(file, length));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Check that its length is now 3 and that it contains only bits of test data.
+ EXPECT_EQ(length, GetFileSize("file"));
+ EXPECT_EQ(length, base::ReadFile(platform_path, data, length));
+ for (int i = 0; i < length; ++i)
+ EXPECT_EQ(test_data[i], data[i]);
+
+ // Truncate is not a 'read' access. (Here expected access count is 1
+ // since we made 1 read access for GetMetadata.)
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTruncateFailureByQuota) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ FileSystemURL file(CreateFile("dir/file"));
+
+ GrantQuotaForCurrentUsage();
+ AddQuota(10);
+
+ EXPECT_EQ(base::File::FILE_OK, Truncate(file, 10));
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(10, GetFileSize("dir/file"));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, Truncate(file, 11));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(10, GetFileSize("dir/file"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestTouchFile) {
+ FileSystemURL file(CreateFile("file"));
+ base::FilePath platform_path = PlatformPath("file");
+
+ base::File::Info info;
+ EXPECT_TRUE(base::GetFileInfo(platform_path, &info));
+ EXPECT_FALSE(info.is_directory);
+ EXPECT_EQ(0, info.size);
+ const base::Time last_modified = info.last_modified;
+ const base::Time last_accessed = info.last_accessed;
+
+ const base::Time new_modified_time = base::Time::UnixEpoch();
+ const base::Time new_accessed_time = new_modified_time +
+ base::TimeDelta::FromHours(77);
+ ASSERT_NE(last_modified, new_modified_time);
+ ASSERT_NE(last_accessed, new_accessed_time);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ TouchFile(file, new_accessed_time, new_modified_time));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_TRUE(base::GetFileInfo(platform_path, &info));
+ // We compare as time_t here to lower our resolution, to avoid false
+ // negatives caused by conversion to the local filesystem's native
+ // representation and back.
+ EXPECT_EQ(new_modified_time.ToTimeT(), info.last_modified.ToTimeT());
+ EXPECT_EQ(new_accessed_time.ToTimeT(), info.last_accessed.ToTimeT());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateSnapshotFile) {
+ FileSystemURL dir(CreateDirectory("dir"));
+
+ // Create a file for the testing.
+ EXPECT_EQ(base::File::FILE_OK, DirectoryExists(dir));
+ FileSystemURL file(CreateFile("dir/file"));
+ EXPECT_EQ(base::File::FILE_OK, FileExists(file));
+
+ // See if we can get a 'snapshot' file info for the file.
+ // Since FileSystemOperationImpl assumes the file exists in the local
+ // directory it should just returns the same metadata and platform_path
+ // as the file itself.
+ EXPECT_EQ(base::File::FILE_OK, CreateSnapshotFile(file));
+ EXPECT_FALSE(info().is_directory);
+ EXPECT_EQ(PlatformPath("dir/file"), path());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // The FileSystemOpration implementation does not create a
+ // shareable file reference.
+ EXPECT_EQ(NULL, shareable_file_ref());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestMoveSuccessSrcDirRecursiveWithQuota) {
+ FileSystemURL src(CreateDirectory("src"));
+ int src_path_cost = GetUsage();
+
+ FileSystemURL dest(CreateDirectory("dest"));
+ FileSystemURL child_file1(CreateFile("src/file1"));
+ FileSystemURL child_file2(CreateFile("src/file2"));
+ FileSystemURL child_dir(CreateDirectory("src/dir"));
+ FileSystemURL grandchild_file1(CreateFile("src/dir/file1"));
+ FileSystemURL grandchild_file2(CreateFile("src/dir/file2"));
+
+ int total_path_cost = GetUsage();
+ EXPECT_EQ(0, GetDataSizeOnDisk());
+
+ EXPECT_EQ(base::File::FILE_OK, Truncate(child_file1, 5000));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(child_file2, 400));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(grandchild_file1, 30));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(grandchild_file2, 2));
+
+ const int64_t all_file_size = 5000 + 400 + 30 + 2;
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(all_file_size + total_path_cost, GetUsage());
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Move(src, dest, FileSystemOperation::OPTION_NONE));
+
+ EXPECT_FALSE(DirectoryExists("src/dir"));
+ EXPECT_FALSE(FileExists("src/dir/file2"));
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/file2"));
+
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(all_file_size + total_path_cost - src_path_cost,
+ GetUsage());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCopySuccessSrcDirRecursiveWithQuota) {
+ FileSystemURL src(CreateDirectory("src"));
+ FileSystemURL dest1(CreateDirectory("dest1"));
+ FileSystemURL dest2(CreateDirectory("dest2"));
+
+ int64_t usage = GetUsage();
+ FileSystemURL child_file1(CreateFile("src/file1"));
+ FileSystemURL child_file2(CreateFile("src/file2"));
+ FileSystemURL child_dir(CreateDirectory("src/dir"));
+ int64_t child_path_cost = GetUsage() - usage;
+ usage += child_path_cost;
+
+ FileSystemURL grandchild_file1(CreateFile("src/dir/file1"));
+ FileSystemURL grandchild_file2(CreateFile("src/dir/file2"));
+ int64_t total_path_cost = GetUsage();
+ int64_t grandchild_path_cost = total_path_cost - usage;
+
+ EXPECT_EQ(0, GetDataSizeOnDisk());
+
+ EXPECT_EQ(base::File::FILE_OK, Truncate(child_file1, 8000));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(child_file2, 700));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(grandchild_file1, 60));
+ EXPECT_EQ(base::File::FILE_OK, Truncate(grandchild_file2, 5));
+
+ const int64_t child_file_size = 8000 + 700;
+ const int64_t grandchild_file_size = 60 + 5;
+ const int64_t all_file_size = child_file_size + grandchild_file_size;
+ int64_t expected_usage = all_file_size + total_path_cost;
+
+ usage = GetUsage();
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, usage);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src, dest1, FileSystemOperation::OPTION_NONE));
+
+ expected_usage += all_file_size + child_path_cost + grandchild_path_cost;
+ EXPECT_TRUE(DirectoryExists("src/dir"));
+ EXPECT_TRUE(FileExists("src/dir/file2"));
+ EXPECT_TRUE(DirectoryExists("dest1/dir"));
+ EXPECT_TRUE(FileExists("dest1/dir/file2"));
+
+ EXPECT_EQ(2 * all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, GetUsage());
+
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(child_dir, dest2, FileSystemOperation::OPTION_NONE));
+
+ expected_usage += grandchild_file_size + grandchild_path_cost;
+ usage = GetUsage();
+ EXPECT_EQ(2 * child_file_size + 3 * grandchild_file_size,
+ GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, usage);
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCopySuccessSrcFileWithDifferentFileSize) {
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_file(CreateFile("dest"));
+
+ EXPECT_EQ(base::File::FILE_OK, Truncate(dest_file, 6));
+ EXPECT_EQ(base::File::FILE_OK,
+ Copy(src_file, dest_file, FileSystemOperation::OPTION_NONE));
+ EXPECT_EQ(0, GetFileSize("dest"));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc b/chromium/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc
new file mode 100644
index 00000000000..f50de10dd54
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright 2013 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 <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#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_task_runner_handle.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/blob_url_request_job.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/local_file_util.h"
+#include "storage/browser/test/mock_blob_url_request_context.h"
+#include "storage/browser/test/mock_file_change_observer.h"
+#include "storage/browser/test/mock_quota_manager.h"
+#include "storage/browser/test/test_file_system_backend.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::FileSystemOperation;
+using storage::FileSystemOperationRunner;
+using storage::FileSystemURL;
+using content::MockBlobURLRequestContext;
+using content::ScopedTextBlob;
+
+namespace content {
+
+namespace {
+
+const GURL kOrigin("http://example.com");
+const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest;
+
+void AssertStatusEq(base::File::Error expected, base::File::Error actual) {
+ ASSERT_EQ(expected, actual);
+}
+
+} // namespace
+
+class FileSystemOperationImplWriteTest : public testing::Test {
+ public:
+ FileSystemOperationImplWriteTest()
+ : status_(base::File::FILE_OK),
+ cancel_status_(base::File::FILE_ERROR_FAILED),
+ bytes_written_(0),
+ complete_(false),
+ weak_factory_(this) {
+ change_observers_ =
+ storage::MockFileChangeObserver::CreateList(&change_observer_);
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+
+ quota_manager_ =
+ new MockQuotaManager(false /* is_incognito */, dir_.GetPath(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ NULL /* special storage policy */);
+ virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file"));
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ quota_manager_->proxy(), dir_.GetPath());
+ url_request_context_.reset(
+ new MockBlobURLRequestContext(file_system_context_.get()));
+
+ file_system_context_->operation_runner()->CreateFile(
+ URLForPath(virtual_path_), true /* exclusive */,
+ base::Bind(&AssertStatusEq, base::File::FILE_OK));
+
+ static_cast<TestFileSystemBackend*>(
+ file_system_context_->GetFileSystemBackend(kFileSystemType))
+ ->AddFileChangeObserver(change_observer());
+ }
+
+ void TearDown() override {
+ quota_manager_ = NULL;
+ file_system_context_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ base::File::Error status() const { return status_; }
+ base::File::Error cancel_status() const { return cancel_status_; }
+ void add_bytes_written(int64_t bytes, bool complete) {
+ bytes_written_ += bytes;
+ EXPECT_FALSE(complete_);
+ complete_ = complete;
+ }
+ int64_t bytes_written() const { return bytes_written_; }
+ bool complete() const { return complete_; }
+
+ protected:
+ const storage::ChangeObserverList& change_observers() const {
+ return change_observers_;
+ }
+
+ storage::MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ FileSystemURL URLForPath(const base::FilePath& path) const {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, path);
+ }
+
+ // Callback function for recording test results.
+ FileSystemOperation::WriteCallback RecordWriteCallback() {
+ return base::Bind(&FileSystemOperationImplWriteTest::DidWrite,
+ weak_factory_.GetWeakPtr());
+ }
+
+ FileSystemOperation::StatusCallback RecordCancelCallback() {
+ return base::Bind(&FileSystemOperationImplWriteTest::DidCancel,
+ weak_factory_.GetWeakPtr());
+ }
+
+ void DidWrite(base::File::Error status, int64_t bytes, bool complete) {
+ if (status == base::File::FILE_OK) {
+ add_bytes_written(bytes, complete);
+ if (complete)
+ base::MessageLoop::current()->QuitWhenIdle();
+ } else {
+ EXPECT_FALSE(complete_);
+ EXPECT_EQ(status_, base::File::FILE_OK);
+ complete_ = true;
+ status_ = status;
+ if (base::RunLoop::IsRunningOnCurrentThread())
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+ }
+
+ void DidCancel(base::File::Error status) { cancel_status_ = status; }
+
+ const MockBlobURLRequestContext& url_request_context() const {
+ return *url_request_context_;
+ }
+
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+ scoped_refptr<MockQuotaManager> quota_manager_;
+
+ base::MessageLoopForIO loop_;
+
+ base::ScopedTempDir dir_;
+ base::FilePath virtual_path_;
+
+ // For post-operation status.
+ base::File::Error status_;
+ base::File::Error cancel_status_;
+ int64_t bytes_written_;
+ bool complete_;
+
+ std::unique_ptr<MockBlobURLRequestContext> url_request_context_;
+
+ storage::MockFileChangeObserver change_observer_;
+ storage::ChangeObserverList change_observers_;
+
+ base::WeakPtrFactory<FileSystemOperationImplWriteTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplWriteTest);
+};
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteSuccess) {
+ ScopedTextBlob blob(url_request_context(), "blob-id:success",
+ "Hello, world!\n");
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(14, bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteZero) {
+ ScopedTextBlob blob(url_request_context(), "blob_id:zero", "");
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidBlobUrl) {
+ std::unique_ptr<storage::BlobDataHandle> null_handle;
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_), std::move(null_handle),
+ 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_FAILED, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidFile) {
+ ScopedTextBlob blob(url_request_context(), "blob_id:writeinvalidfile",
+ "It\'ll not be written.");
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(),
+ URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteDir) {
+ base::FilePath virtual_dir_path(FILE_PATH_LITERAL("d"));
+ file_system_context_->operation_runner()->CreateDirectory(
+ URLForPath(virtual_dir_path), true /* exclusive */, false /* recursive */,
+ base::Bind(&AssertStatusEq, base::File::FILE_OK));
+
+ ScopedTextBlob blob(url_request_context(), "blob:writedir",
+ "It\'ll not be written, too.");
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_dir_path),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(0, bytes_written());
+ // TODO(kinuko): This error code is platform- or fileutil- dependent
+ // right now. Make it return File::FILE_ERROR_NOT_A_FILE in every case.
+ EXPECT_TRUE(status() == base::File::FILE_ERROR_NOT_A_FILE ||
+ status() == base::File::FILE_ERROR_ACCESS_DENIED ||
+ status() == base::File::FILE_ERROR_FAILED);
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) {
+ ScopedTextBlob blob(url_request_context(), "blob:success", "Hello, world!\n");
+ quota_manager_->SetQuota(
+ kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10);
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ base::RunLoop().Run();
+
+ EXPECT_EQ(10, bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelSuccessfulWrite) {
+ ScopedTextBlob blob(url_request_context(), "blob:success", "Hello, world!\n");
+ FileSystemOperationRunner::OperationID id =
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
+ // We use RunAllPendings() instead of Run() here, because we won't dispatch
+ // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
+ // to run another write cycle.
+ base::RunLoop().RunUntilIdle();
+
+ // Issued Cancel() before receiving any response from Write(),
+ // so nothing should have happen.
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_ABORT, status());
+ EXPECT_EQ(base::File::FILE_OK, cancel_status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelFailingWrite) {
+ ScopedTextBlob blob(url_request_context(), "blob:writeinvalidfile",
+ "It\'ll not be written.");
+ FileSystemOperationRunner::OperationID id =
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(),
+ URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
+ blob.GetBlobDataHandle(), 0, RecordWriteCallback());
+ file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
+ // We use RunAllPendings() instead of Run() here, because we won't dispatch
+ // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
+ // to run another write cycle.
+ base::RunLoop().RunUntilIdle();
+
+ // Issued Cancel() before receiving any response from Write(),
+ // so nothing should have happen.
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_ABORT, status());
+ EXPECT_EQ(base::File::FILE_OK, cancel_status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+// TODO(ericu,dmikurube,kinuko): Add more tests for cancel cases.
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_quota_client_unittest.cc b/chromium/storage/browser/fileapi/file_system_quota_client_unittest.cc
new file mode 100644
index 00000000000..ccf04f85fae
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_quota_client_unittest.cc
@@ -0,0 +1,572 @@
+// Copyright 2013 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 <stdint.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_quota_client.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "storage/common/quota/quota_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemQuotaClient;
+using storage::FileSystemURL;
+
+namespace content {
+namespace {
+
+const char kDummyURL1[] = "http://www.dummy.org";
+const char kDummyURL2[] = "http://www.example.com";
+const char kDummyURL3[] = "http://www.bleh";
+
+// Declared to shorten the variable names.
+const storage::StorageType kTemporary = storage::kStorageTypeTemporary;
+const storage::StorageType kPersistent = storage::kStorageTypePersistent;
+
+} // namespace
+
+class FileSystemQuotaClientTest : public testing::Test {
+ public:
+ FileSystemQuotaClientTest()
+ : additional_callback_count_(0),
+ deletion_status_(storage::kQuotaStatusUnknown),
+ weak_factory_(this) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, data_dir_.GetPath());
+ }
+
+ struct TestFile {
+ bool isDirectory;
+ const char* name;
+ int64_t size;
+ const char* origin_url;
+ storage::StorageType type;
+ };
+
+ protected:
+ FileSystemQuotaClient* NewQuotaClient(bool is_incognito) {
+ return new FileSystemQuotaClient(file_system_context_.get(), is_incognito);
+ }
+
+ void GetOriginUsageAsync(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ storage::StorageType type) {
+ quota_client->GetOriginUsage(
+ GURL(origin_url), type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ int64_t GetOriginUsage(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ storage::StorageType type) {
+ GetOriginUsageAsync(quota_client, origin_url, type);
+ base::RunLoop().RunUntilIdle();
+ return usage_;
+ }
+
+ const std::set<GURL>& GetOriginsForType(FileSystemQuotaClient* quota_client,
+ storage::StorageType type) {
+ origins_.clear();
+ quota_client->GetOriginsForType(
+ type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetOrigins,
+ weak_factory_.GetWeakPtr()));
+ base::RunLoop().RunUntilIdle();
+ return origins_;
+ }
+
+ const std::set<GURL>& GetOriginsForHost(FileSystemQuotaClient* quota_client,
+ storage::StorageType type,
+ const std::string& host) {
+ origins_.clear();
+ quota_client->GetOriginsForHost(
+ type, host,
+ base::Bind(&FileSystemQuotaClientTest::OnGetOrigins,
+ weak_factory_.GetWeakPtr()));
+ base::RunLoop().RunUntilIdle();
+ return origins_;
+ }
+
+ void RunAdditionalOriginUsageTask(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ storage::StorageType type) {
+ quota_client->GetOriginUsage(
+ GURL(origin_url), type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetAdditionalUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ bool CreateFileSystemDirectory(const base::FilePath& file_path,
+ const std::string& origin_url,
+ storage::StorageType storage_type) {
+ storage::FileSystemType type =
+ storage::QuotaStorageTypeToFileSystemType(storage_type);
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL(origin_url), type, file_path);
+
+ base::File::Error result =
+ AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), url);
+ return result == base::File::FILE_OK;
+ }
+
+ bool CreateFileSystemFile(const base::FilePath& file_path,
+ int64_t file_size,
+ const std::string& origin_url,
+ storage::StorageType storage_type) {
+ if (file_path.empty())
+ return false;
+
+ storage::FileSystemType type =
+ storage::QuotaStorageTypeToFileSystemType(storage_type);
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL(origin_url), type, file_path);
+
+ base::File::Error result =
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
+ if (result != base::File::FILE_OK)
+ return false;
+
+ result = AsyncFileTestHelper::TruncateFile(
+ file_system_context_.get(), url, file_size);
+ return result == base::File::FILE_OK;
+ }
+
+ void InitializeOriginFiles(FileSystemQuotaClient* quota_client,
+ const TestFile* files,
+ int num_files) {
+ for (int i = 0; i < num_files; i++) {
+ base::FilePath path = base::FilePath().AppendASCII(files[i].name);
+ if (files[i].isDirectory) {
+ ASSERT_TRUE(CreateFileSystemDirectory(
+ path, files[i].origin_url, files[i].type));
+ if (path.empty()) {
+ // Create the usage cache.
+ // HACK--we always create the root [an empty path] first. If we
+ // create it later, this will fail due to a quota mismatch. If we
+ // call this before we create the root, it succeeds, but hasn't
+ // actually created the cache.
+ ASSERT_EQ(0, GetOriginUsage(
+ quota_client, files[i].origin_url, files[i].type));
+ }
+ } else {
+ ASSERT_TRUE(CreateFileSystemFile(
+ path, files[i].size, files[i].origin_url, files[i].type));
+ }
+ }
+ }
+
+ // This is a bit fragile--it depends on the test data always creating a
+ // directory before adding a file or directory to it, so that we can just
+ // count the basename of each addition. A recursive creation of a path, which
+ // created more than one directory in a single shot, would break this.
+ int64_t ComputeFilePathsCostForOriginAndType(const TestFile* files,
+ int num_files,
+ const std::string& origin_url,
+ storage::StorageType type) {
+ int64_t file_paths_cost = 0;
+ for (int i = 0; i < num_files; i++) {
+ if (files[i].type == type &&
+ GURL(files[i].origin_url) == GURL(origin_url)) {
+ base::FilePath path = base::FilePath().AppendASCII(files[i].name);
+ if (!path.empty()) {
+ file_paths_cost +=
+ storage::ObfuscatedFileUtil::ComputeFilePathCost(path);
+ }
+ }
+ }
+ return file_paths_cost;
+ }
+
+ void DeleteOriginData(FileSystemQuotaClient* quota_client,
+ const std::string& origin,
+ storage::StorageType type) {
+ deletion_status_ = storage::kQuotaStatusUnknown;
+ quota_client->DeleteOriginData(
+ GURL(origin), type,
+ base::Bind(&FileSystemQuotaClientTest::OnDeleteOrigin,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ int64_t usage() const { return usage_; }
+ storage::QuotaStatusCode status() { return deletion_status_; }
+ int additional_callback_count() const { return additional_callback_count_; }
+ void set_additional_callback_count(int count) {
+ additional_callback_count_ = count;
+ }
+
+ private:
+ void OnGetUsage(int64_t usage) { usage_ = usage; }
+
+ void OnGetOrigins(const std::set<GURL>& origins) {
+ origins_ = origins;
+ }
+
+ void OnGetAdditionalUsage(int64_t usage_unused) {
+ ++additional_callback_count_;
+ }
+
+ void OnDeleteOrigin(storage::QuotaStatusCode status) {
+ deletion_status_ = status;
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+ int64_t usage_;
+ int additional_callback_count_;
+ std::set<GURL> origins_;
+ storage::QuotaStatusCode deletion_status_;
+ base::WeakPtrFactory<FileSystemQuotaClientTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClientTest);
+};
+
+TEST_F(FileSystemQuotaClientTest, NoFileSystemTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+}
+
+TEST_F(FileSystemQuotaClientTest, NoFileTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, OneFileTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 4921, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, arraysize(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(4921 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, TwoFilesTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 10310, kDummyURL1, kTemporary},
+ {false, "bar", 41, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, arraysize(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(10310 + 41 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 0, kDummyURL1, kTemporary},
+ {false, "bar", 0, kDummyURL1, kTemporary},
+ {false, "baz", 0, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, arraysize(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dirtest", 0, kDummyURL1, kTemporary},
+ {false, "dirtest/foo", 11921, kDummyURL1, kTemporary},
+ {false, "bar", 4814, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, arraysize(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(11921 + 4814 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, MultiTypeTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dirtest", 0, kDummyURL1, kTemporary},
+ {false, "dirtest/foo", 133, kDummyURL1, kTemporary},
+ {false, "bar", 14, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL1, kPersistent},
+ {true, "dirtest", 0, kDummyURL1, kPersistent},
+ {false, "dirtest/foo", 193, kDummyURL1, kPersistent},
+ {false, "bar", 9, kDummyURL1, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost_temporary =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL1, kTemporary);
+ const int64_t file_paths_cost_persistent =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(133 + 14 + file_paths_cost_temporary,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(193 + 9 + file_paths_cost_persistent,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, MultiDomainTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dir1", 0, kDummyURL1, kTemporary},
+ {false, "dir1/foo", 1331, kDummyURL1, kTemporary},
+ {false, "bar", 134, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL1, kPersistent},
+ {true, "dir2", 0, kDummyURL1, kPersistent},
+ {false, "dir2/foo", 1903, kDummyURL1, kPersistent},
+ {false, "bar", 19, kDummyURL1, kPersistent},
+ {true, NULL, 0, kDummyURL2, kTemporary},
+ {true, "dom", 0, kDummyURL2, kTemporary},
+ {false, "dom/fan", 1319, kDummyURL2, kTemporary},
+ {false, "bar", 113, kDummyURL2, kTemporary},
+ {true, NULL, 0, kDummyURL2, kPersistent},
+ {true, "dom", 0, kDummyURL2, kPersistent},
+ {false, "dom/fan", 2013, kDummyURL2, kPersistent},
+ {false, "baz", 18, kDummyURL2, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost_temporary1 =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL1, kTemporary);
+ const int64_t file_paths_cost_persistent1 =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL1, kPersistent);
+ const int64_t file_paths_cost_temporary2 =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL2, kTemporary);
+ const int64_t file_paths_cost_persistent2 =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ kDummyURL2, kPersistent);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(1331 + 134 + file_paths_cost_temporary1,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(1903 + 19 + file_paths_cost_persistent1,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+ EXPECT_EQ(1319 + 113 + file_paths_cost_temporary2,
+ GetOriginUsage(quota_client.get(), kDummyURL2, kTemporary));
+ EXPECT_EQ(2013 + 18 + file_paths_cost_persistent2,
+ GetOriginUsage(quota_client.get(), kDummyURL2, kPersistent));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 11, kDummyURL1, kTemporary},
+ {false, "bar", 22, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, arraysize(kFiles), kDummyURL1, kTemporary);
+
+ // Dispatching three GetUsage tasks.
+ set_additional_callback_count(0);
+ GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(11 + 22 + file_paths_cost, usage());
+ EXPECT_EQ(2, additional_callback_count());
+
+ // Once more, in a different order.
+ set_additional_callback_count(0);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(11 + 22 + file_paths_cost, usage());
+ EXPECT_EQ(2, additional_callback_count());
+}
+
+TEST_F(FileSystemQuotaClientTest, GetOriginsForType) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL2, kTemporary},
+ {true, NULL, 0, kDummyURL3, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+
+ std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary);
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL1)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL2)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL3)) == origins.end());
+}
+
+TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const char* kURL1 = "http://foo.com/";
+ const char* kURL2 = "https://foo.com/";
+ const char* kURL3 = "http://foo.com:1/";
+ const char* kURL4 = "http://foo2.com/";
+ const char* kURL5 = "http://foo.com:2/";
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kURL1, kTemporary},
+ {true, NULL, 0, kURL2, kTemporary},
+ {true, NULL, 0, kURL3, kTemporary},
+ {true, NULL, 0, kURL4, kTemporary},
+ {true, NULL, 0, kURL5, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+
+ std::set<GURL> origins = GetOriginsForHost(
+ quota_client.get(), kTemporary, "foo.com");
+ EXPECT_EQ(3U, origins.size());
+ EXPECT_TRUE(origins.find(GURL(kURL1)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL2)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL3)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL4)) == origins.end()); // Different host.
+ EXPECT_TRUE(origins.find(GURL(kURL5)) == origins.end()); // Different type.
+}
+
+TEST_F(FileSystemQuotaClientTest, IncognitoTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(true));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 10, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+
+ // Having files in the usual directory wouldn't affect the result
+ // queried in incognito mode.
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+
+ std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary);
+ EXPECT_EQ(0U, origins.size());
+ origins = GetOriginsForHost(quota_client.get(), kTemporary, "www.dummy.org");
+ EXPECT_EQ(0U, origins.size());
+}
+
+TEST_F(FileSystemQuotaClientTest, DeleteOriginTest) {
+ std::unique_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, "http://foo.com/", kTemporary},
+ {false, "a", 1, "http://foo.com/", kTemporary},
+ {true, NULL, 0, "https://foo.com/", kTemporary},
+ {false, "b", 2, "https://foo.com/", kTemporary},
+ {true, NULL, 0, "http://foo.com/", kPersistent},
+ {false, "c", 4, "http://foo.com/", kPersistent},
+ {true, NULL, 0, "http://bar.com/", kTemporary},
+ {false, "d", 8, "http://bar.com/", kTemporary},
+ {true, NULL, 0, "http://bar.com/", kPersistent},
+ {false, "e", 16, "http://bar.com/", kPersistent},
+ {true, NULL, 0, "https://bar.com/", kPersistent},
+ {false, "f", 32, "https://bar.com/", kPersistent},
+ {true, NULL, 0, "https://bar.com/", kTemporary},
+ {false, "g", 64, "https://bar.com/", kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, arraysize(kFiles));
+ const int64_t file_paths_cost_temporary_foo_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ "https://foo.com/", kTemporary);
+ const int64_t file_paths_cost_persistent_foo =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ "http://foo.com/", kPersistent);
+ const int64_t file_paths_cost_temporary_bar =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ "http://bar.com/", kTemporary);
+ const int64_t file_paths_cost_temporary_bar_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ "https://bar.com/", kTemporary);
+ const int64_t file_paths_cost_persistent_bar_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, arraysize(kFiles),
+ "https://bar.com/", kPersistent);
+
+ DeleteOriginData(quota_client.get(), "http://foo.com/", kTemporary);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(storage::kQuotaStatusOk, status());
+
+ DeleteOriginData(quota_client.get(), "http://bar.com/", kPersistent);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(storage::kQuotaStatusOk, status());
+
+ DeleteOriginData(quota_client.get(), "http://buz.com/", kTemporary);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(storage::kQuotaStatusOk, status());
+
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://foo.com/", kTemporary));
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://bar.com/", kPersistent));
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://buz.com/", kTemporary));
+
+ EXPECT_EQ(2 + file_paths_cost_temporary_foo_https,
+ GetOriginUsage(quota_client.get(),
+ "https://foo.com/",
+ kTemporary));
+ EXPECT_EQ(4 + file_paths_cost_persistent_foo,
+ GetOriginUsage(quota_client.get(),
+ "http://foo.com/",
+ kPersistent));
+ EXPECT_EQ(8 + file_paths_cost_temporary_bar,
+ GetOriginUsage(quota_client.get(),
+ "http://bar.com/",
+ kTemporary));
+ EXPECT_EQ(32 + file_paths_cost_persistent_bar_https,
+ GetOriginUsage(quota_client.get(),
+ "https://bar.com/",
+ kPersistent));
+ EXPECT_EQ(64 + file_paths_cost_temporary_bar_https,
+ GetOriginUsage(quota_client.get(),
+ "https://bar.com/",
+ kTemporary));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_url_request_job.cc b/chromium/storage/browser/fileapi/file_system_url_request_job.cc
index 30fb47d2589..b68a43a2d93 100644
--- a/chromium/storage/browser/fileapi/file_system_url_request_job.cc
+++ b/chromium/storage/browser/fileapi/file_system_url_request_job.cc
@@ -143,12 +143,6 @@ void FileSystemURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
*info = *response_info_;
}
-int FileSystemURLRequestJob::GetResponseCode() const {
- if (response_info_)
- return 200;
- return URLRequestJob::GetResponseCode();
-}
-
void FileSystemURLRequestJob::StartAsync() {
if (!request_)
return;
diff --git a/chromium/storage/browser/fileapi/file_system_url_request_job.h b/chromium/storage/browser/fileapi/file_system_url_request_job.h
index 59b286abc9d..7ed1042676e 100644
--- a/chromium/storage/browser/fileapi/file_system_url_request_job.h
+++ b/chromium/storage/browser/fileapi/file_system_url_request_job.h
@@ -45,7 +45,6 @@ class STORAGE_EXPORT FileSystemURLRequestJob : public net::URLRequestJob {
bool IsRedirectResponse(GURL* location, int* http_status_code) override;
void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
void GetResponseInfo(net::HttpResponseInfo* info) override;
- int GetResponseCode() const override;
// FilterContext methods (via URLRequestJob):
bool GetMimeType(std::string* mime_type) const override;
diff --git a/chromium/storage/browser/fileapi/file_system_url_request_job_unittest.cc b/chromium/storage/browser/fileapi/file_system_url_request_job_unittest.cc
new file mode 100644
index 00000000000..6a399336fcd
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_system_url_request_job_unittest.cc
@@ -0,0 +1,487 @@
+// Copyright 2013 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 <stddef.h>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_url_request_job.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_backend.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemContext;
+using storage::FileSystemURL;
+using storage::FileSystemURLRequestJob;
+
+namespace content {
+namespace {
+
+// We always use the TEMPORARY FileSystem in this test.
+const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
+const char kTestFileData[] = "0123456789";
+
+void FillBuffer(char* buffer, size_t len) {
+ base::RandBytes(buffer, len);
+}
+
+const char kValidExternalMountPoint[] = "mnt_name";
+
+// An auto mounter that will try to mount anything for |storage_domain| =
+// "automount", but will only succeed for the mount point "mnt_name".
+bool TestAutoMountForURLRequest(
+ const net::URLRequest* /*url_request*/,
+ const storage::FileSystemURL& filesystem_url,
+ const std::string& storage_domain,
+ const base::Callback<void(base::File::Error result)>& callback) {
+ if (storage_domain != "automount")
+ return false;
+ std::vector<base::FilePath::StringType> components;
+ filesystem_url.path().GetComponents(&components);
+ std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe();
+
+ if (mount_point == kValidExternalMountPoint) {
+ storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ kValidExternalMountPoint,
+ storage::kFileSystemTypeTest,
+ storage::FileSystemMountOption(),
+ base::FilePath());
+ callback.Run(base::File::FILE_OK);
+ } else {
+ callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+ }
+ return true;
+}
+
+class FileSystemURLRequestJobFactory : public net::URLRequestJobFactory {
+ public:
+ FileSystemURLRequestJobFactory(const std::string& storage_domain,
+ FileSystemContext* context)
+ : storage_domain_(storage_domain), file_system_context_(context) {
+ }
+
+ net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return new storage::FileSystemURLRequestJob(
+ request, network_delegate, storage_domain_, file_system_context_);
+ }
+
+ net::URLRequestJob* MaybeInterceptRedirect(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const GURL& location) const override {
+ return nullptr;
+ }
+
+ net::URLRequestJob* MaybeInterceptResponse(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return nullptr;
+ }
+
+ bool IsHandledProtocol(const std::string& scheme) const override {
+ return true;
+ }
+
+ bool IsSafeRedirectTarget(const GURL& location) const override {
+ return false;
+ }
+
+ private:
+ std::string storage_domain_;
+ FileSystemContext* file_system_context_;
+};
+
+} // namespace
+
+class FileSystemURLRequestJobTest : public testing::Test {
+ protected:
+ FileSystemURLRequestJobTest() : weak_factory_(this) {
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ // We use the main thread so that we can get the root path synchronously.
+ // TODO(adamk): Run this on the FILE thread we've created as well.
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, temp_dir_.GetPath());
+
+ file_system_context_->OpenFileSystem(
+ GURL("http://remote/"),
+ storage::kFileSystemTypeTemporary,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem,
+ weak_factory_.GetWeakPtr()));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void TearDown() override {
+ // FileReader posts a task to close the file in destructor.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void SetUpAutoMountContext() {
+ base::FilePath mnt_point =
+ temp_dir_.GetPath().AppendASCII("auto_mount_dir");
+ ASSERT_TRUE(base::CreateDirectory(mnt_point));
+
+ std::vector<std::unique_ptr<storage::FileSystemBackend>>
+ additional_providers;
+ additional_providers.push_back(base::MakeUnique<TestFileSystemBackend>(
+ base::ThreadTaskRunnerHandle::Get().get(), mnt_point));
+
+ std::vector<storage::URLRequestAutoMountHandler> handlers;
+ handlers.push_back(base::Bind(&TestAutoMountForURLRequest));
+
+ file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting(
+ NULL, std::move(additional_providers), handlers, temp_dir_.GetPath());
+
+ ASSERT_EQ(static_cast<int>(sizeof(kTestFileData)) - 1,
+ base::WriteFile(mnt_point.AppendASCII("foo"), kTestFileData,
+ sizeof(kTestFileData) - 1));
+ }
+
+ void OnOpenFileSystem(const GURL& root_url,
+ const std::string& name,
+ base::File::Error result) {
+ ASSERT_EQ(base::File::FILE_OK, result);
+ }
+
+ void TestRequestHelper(const GURL& url,
+ const net::HttpRequestHeaders* headers,
+ bool run_to_completion,
+ FileSystemContext* file_system_context) {
+ delegate_.reset(new net::TestDelegate());
+ // Make delegate_ exit the MessageLoop when the request is done.
+ delegate_->set_quit_on_complete(true);
+ delegate_->set_quit_on_redirect(true);
+
+ job_factory_.reset(new FileSystemURLRequestJobFactory(
+ url.GetOrigin().host(), file_system_context));
+ empty_context_.set_job_factory(job_factory_.get());
+
+ request_ = empty_context_.CreateRequest(url, net::DEFAULT_PRIORITY,
+ delegate_.get(),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ if (headers)
+ request_->SetExtraRequestHeaders(*headers);
+
+ request_->Start();
+ ASSERT_TRUE(request_->is_pending()); // verify that we're starting async
+ if (run_to_completion)
+ base::RunLoop().Run();
+ }
+
+ void TestRequest(const GURL& url) {
+ TestRequestHelper(url, NULL, true, file_system_context_.get());
+ }
+
+ void TestRequestWithContext(const GURL& url,
+ FileSystemContext* file_system_context) {
+ TestRequestHelper(url, NULL, true, file_system_context);
+ }
+
+ void TestRequestWithHeaders(const GURL& url,
+ const net::HttpRequestHeaders* headers) {
+ TestRequestHelper(url, headers, true, file_system_context_.get());
+ }
+
+ void TestRequestNoRun(const GURL& url) {
+ TestRequestHelper(url, NULL, false, file_system_context_.get());
+ }
+
+ void CreateDirectory(const base::StringPiece& dir_name) {
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"),
+ storage::kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(dir_name));
+ ASSERT_EQ(
+ base::File::FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), url));
+ }
+
+ void WriteFile(const base::StringPiece& file_name,
+ const char* buf, int buf_size) {
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"),
+ storage::kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(file_name));
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFileWithData(
+ file_system_context_.get(), url, buf, buf_size));
+ }
+
+ GURL CreateFileSystemURL(const std::string& path) {
+ return GURL(kFileSystemURLPrefix + path);
+ }
+
+ // Temp directory is at the top because it must be deleted last.
+ base::ScopedTempDir temp_dir_;
+
+ // The message loop must be deleted second to last.
+ base::MessageLoopForIO message_loop_;
+
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+
+ net::URLRequestContext empty_context_;
+ std::unique_ptr<FileSystemURLRequestJobFactory> job_factory_;
+
+ // NOTE: order matters, request must die before delegate
+ std::unique_ptr<net::TestDelegate> delegate_;
+ std::unique_ptr<net::URLRequest> request_;
+
+ base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_;
+};
+
+namespace {
+
+TEST_F(FileSystemURLRequestJobTest, FileTest) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ TestRequest(CreateFileSystemURL("file1.dat"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_EQ(kTestFileData, delegate_->data_received());
+ EXPECT_EQ(200, request_->GetResponseCode());
+ std::string cache_control;
+ request_->GetResponseHeaderByName("cache-control", &cache_control);
+ EXPECT_EQ("no-cache", cache_control);
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ std::unique_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+ WriteFile("bigfile", buffer.get(), buffer_size);
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - first_byte_position;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(
+ net::HttpRequestHeaders::kRange,
+ net::HttpByteRange::Bounded(
+ first_byte_position, last_byte_position).GetHeaderValue());
+ TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ std::unique_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+ WriteFile("bigfile", buffer.get(), buffer_size);
+
+ const size_t first_byte_position = 500;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + buffer_size);
+
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(
+ net::HttpRequestHeaders::kRange,
+ net::HttpByteRange::RightUnbounded(first_byte_position).GetHeaderValue());
+ TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ "bytes=0-5,10-200,200-300");
+ TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
+ delegate_->request_status());
+}
+
+TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(
+ net::HttpRequestHeaders::kRange,
+ net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
+ TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
+ delegate_->request_status());
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) {
+ CreateDirectory("dir");
+ TestRequest(CreateFileSystemURL("dir"));
+
+ EXPECT_EQ(1, delegate_->received_redirect_count());
+ EXPECT_FALSE(delegate_->request_failed());
+
+ // We've deferred the redirect; now cancel the request to avoid following it.
+ request_->Cancel();
+ base::RunLoop().Run();
+}
+
+TEST_F(FileSystemURLRequestJobTest, InvalidURL) {
+ TestRequest(GURL("filesystem:/foo/bar/baz"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_INVALID_URL, delegate_->request_status());
+}
+
+TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) {
+ TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+}
+
+TEST_F(FileSystemURLRequestJobTest, NoSuchFile) {
+ TestRequest(CreateFileSystemURL("somefile"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+}
+
+TEST_F(FileSystemURLRequestJobTest, Cancel) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ TestRequestNoRun(CreateFileSystemURL("file1.dat"));
+
+ // Run StartAsync() and only StartAsync().
+ base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
+ request_.release());
+ base::RunLoop().RunUntilIdle();
+ // If we get here, success! we didn't crash!
+}
+
+TEST_F(FileSystemURLRequestJobTest, GetMimeType) {
+ const char kFilename[] = "hoge.html";
+
+ std::string mime_type_direct;
+ base::FilePath::StringType extension =
+ base::FilePath().AppendASCII(kFilename).Extension();
+ if (!extension.empty())
+ extension = extension.substr(1);
+ EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
+ extension, &mime_type_direct));
+
+ TestRequest(CreateFileSystemURL(kFilename));
+
+ std::string mime_type_from_job;
+ request_->GetMimeType(&mime_type_from_job);
+ EXPECT_EQ(mime_type_direct, mime_type_from_job);
+}
+
+TEST_F(FileSystemURLRequestJobTest, Incognito) {
+ WriteFile("file", kTestFileData, arraysize(kTestFileData) - 1);
+
+ // Creates a new filesystem context for incognito mode.
+ scoped_refptr<FileSystemContext> file_system_context =
+ CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.GetPath());
+
+ // The request should return NOT_FOUND error if it's in incognito mode.
+ TestRequestWithContext(CreateFileSystemURL("file"),
+ file_system_context.get());
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+
+ // Make sure it returns success with regular (non-incognito) context.
+ TestRequest(CreateFileSystemURL("file"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(kTestFileData, delegate_->data_received());
+ EXPECT_EQ(200, request_->GetResponseCode());
+}
+
+TEST_F(FileSystemURLRequestJobTest, AutoMountFileTest) {
+ SetUpAutoMountContext();
+ TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_EQ(kTestFileData, delegate_->data_received());
+ EXPECT_EQ(200, request_->GetResponseCode());
+ std::string cache_control;
+ request_->GetResponseHeaderByName("cache-control", &cache_control);
+ EXPECT_EQ("no-cache", cache_control);
+
+ ASSERT_TRUE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kValidExternalMountPoint));
+}
+
+TEST_F(FileSystemURLRequestJobTest, AutoMountInvalidRoot) {
+ SetUpAutoMountContext();
+ TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+
+ ASSERT_FALSE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ "invalid"));
+}
+
+TEST_F(FileSystemURLRequestJobTest, AutoMountNoHandler) {
+ SetUpAutoMountContext();
+ TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, delegate_->request_status());
+
+ ASSERT_FALSE(
+ storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kValidExternalMountPoint));
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/file_system_usage_cache.cc b/chromium/storage/browser/fileapi/file_system_usage_cache.cc
index c96021e9fab..c9993d30da3 100644
--- a/chromium/storage/browser/fileapi/file_system_usage_cache.cc
+++ b/chromium/storage/browser/fileapi/file_system_usage_cache.cc
@@ -48,7 +48,7 @@ const int FileSystemUsageCache::kUsageFileSize =
bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path,
int64_t* usage_out) {
TRACE_EVENT0("FileSystem", "UsageCache::GetUsage");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
DCHECK(usage_out);
bool is_valid = true;
uint32_t dirty = 0;
@@ -62,7 +62,7 @@ bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path,
bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path,
uint32_t* dirty_out) {
TRACE_EVENT0("FileSystem", "UsageCache::GetDirty");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
DCHECK(dirty_out);
bool is_valid = true;
uint32_t dirty = 0;
@@ -76,7 +76,7 @@ bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path,
bool FileSystemUsageCache::IncrementDirty(
const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::IncrementDirty");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
bool is_valid = true;
uint32_t dirty = 0;
int64_t usage = 0;
@@ -93,7 +93,7 @@ bool FileSystemUsageCache::IncrementDirty(
bool FileSystemUsageCache::DecrementDirty(
const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::DecrementDirty");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
bool is_valid = true;
uint32_t dirty = 0;
int64_t usage = 0;
@@ -105,7 +105,7 @@ bool FileSystemUsageCache::DecrementDirty(
bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::Invalidate");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
bool is_valid = true;
uint32_t dirty = 0;
int64_t usage = 0;
@@ -117,7 +117,7 @@ bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) {
bool FileSystemUsageCache::IsValid(const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::IsValid");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
bool is_valid = true;
uint32_t dirty = 0;
int64_t usage = 0;
@@ -130,7 +130,7 @@ bool FileSystemUsageCache::AtomicUpdateUsageByDelta(
const base::FilePath& usage_file_path,
int64_t delta) {
TRACE_EVENT0("FileSystem", "UsageCache::AtomicUpdateUsageByDelta");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
bool is_valid = true;
uint32_t dirty = 0;
int64_t usage = 0;
@@ -142,26 +142,26 @@ bool FileSystemUsageCache::AtomicUpdateUsageByDelta(
bool FileSystemUsageCache::UpdateUsage(const base::FilePath& usage_file_path,
int64_t fs_usage) {
TRACE_EVENT0("FileSystem", "UsageCache::UpdateUsage");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
return Write(usage_file_path, true, 0, fs_usage);
}
bool FileSystemUsageCache::Exists(const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::Exists");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
return base::PathExists(usage_file_path);
}
bool FileSystemUsageCache::Delete(const base::FilePath& usage_file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::Delete");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
CloseCacheFiles();
return base::DeleteFile(usage_file_path, false);
}
void FileSystemUsageCache::CloseCacheFiles() {
TRACE_EVENT0("FileSystem", "UsageCache::CloseCacheFiles");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
cache_files_.clear();
timer_.reset();
}
@@ -171,7 +171,7 @@ bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path,
uint32_t* dirty_out,
int64_t* usage_out) {
TRACE_EVENT0("FileSystem", "UsageCache::Read");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
DCHECK(is_valid);
DCHECK(dirty_out);
DCHECK(usage_out);
@@ -207,7 +207,7 @@ bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path,
int32_t dirty,
int64_t usage) {
TRACE_EVENT0("FileSystem", "UsageCache::Write");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
base::Pickle write_pickle;
write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize);
write_pickle.WriteBool(is_valid);
@@ -224,7 +224,7 @@ bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path,
}
base::File* FileSystemUsageCache::GetFile(const base::FilePath& file_path) {
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
if (cache_files_.size() >= kMaxHandleCacheSize)
CloseCacheFiles();
ScheduleCloseTimer();
@@ -250,7 +250,7 @@ base::File* FileSystemUsageCache::GetFile(const base::FilePath& file_path) {
bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
char* buffer,
int64_t buffer_size) {
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
base::File* file = GetFile(file_path);
if (!file)
return false;
@@ -260,7 +260,7 @@ bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
const char* buffer,
int64_t buffer_size) {
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
base::File* file = GetFile(file_path);
if (!file)
return false;
@@ -269,7 +269,7 @@ bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
TRACE_EVENT0("FileSystem", "UsageCache::FlushFile");
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
base::File* file = GetFile(file_path);
if (!file)
return false;
@@ -277,7 +277,7 @@ bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
}
void FileSystemUsageCache::ScheduleCloseTimer() {
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
if (!timer_)
timer_.reset(new TimedTaskHelper(task_runner_.get()));
@@ -292,12 +292,12 @@ void FileSystemUsageCache::ScheduleCloseTimer() {
weak_factory_.GetWeakPtr()));
}
-bool FileSystemUsageCache::CalledOnValidThread() {
- return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread();
+bool FileSystemUsageCache::CalledOnValidSequence() {
+ return !task_runner_.get() || task_runner_->RunsTasksInCurrentSequence();
}
bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) {
- DCHECK(CalledOnValidThread());
+ DCHECK(CalledOnValidSequence());
DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize);
return base::ContainsKey(cache_files_, file_path);
}
diff --git a/chromium/storage/browser/fileapi/file_system_usage_cache.h b/chromium/storage/browser/fileapi/file_system_usage_cache.h
index 98dd9227048..bad0f5fc2a1 100644
--- a/chromium/storage/browser/fileapi/file_system_usage_cache.h
+++ b/chromium/storage/browser/fileapi/file_system_usage_cache.h
@@ -88,7 +88,7 @@ class STORAGE_EXPORT FileSystemUsageCache {
bool HasCacheFileHandle(const base::FilePath& file_path);
- bool CalledOnValidThread();
+ bool CalledOnValidSequence();
std::unique_ptr<TimedTaskHelper> timer_;
std::map<base::FilePath, std::unique_ptr<base::File>> cache_files_;
diff --git a/chromium/storage/browser/fileapi/file_writer_delegate_unittest.cc b/chromium/storage/browser/fileapi/file_writer_delegate_unittest.cc
new file mode 100644
index 00000000000..ca213c1fb74
--- /dev/null
+++ b/chromium/storage/browser/fileapi/file_writer_delegate_unittest.cc
@@ -0,0 +1,516 @@
+// Copyright 2013 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 <stdint.h>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.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/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/io_buffer.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_response_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_quota_util.h"
+#include "storage/browser/fileapi/file_writer_delegate.h"
+#include "storage/browser/fileapi/sandbox_file_stream_writer.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_mount_option.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemURL;
+using storage::FileWriterDelegate;
+
+namespace content {
+
+namespace {
+
+const GURL kOrigin("http://example.com");
+const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest;
+
+const char kData[] = "The quick brown fox jumps over the lazy dog.\n";
+const int kDataSize = arraysize(kData) - 1;
+
+class Result {
+ public:
+ Result()
+ : status_(base::File::FILE_OK),
+ bytes_written_(0),
+ write_status_(FileWriterDelegate::SUCCESS_IO_PENDING) {}
+
+ base::File::Error status() const { return status_; }
+ int64_t bytes_written() const { return bytes_written_; }
+ FileWriterDelegate::WriteProgressStatus write_status() const {
+ return write_status_;
+ }
+
+ void DidWrite(base::File::Error status,
+ int64_t bytes,
+ FileWriterDelegate::WriteProgressStatus write_status) {
+ write_status_ = write_status;
+ if (status == base::File::FILE_OK) {
+ bytes_written_ += bytes;
+ if (write_status_ != FileWriterDelegate::SUCCESS_IO_PENDING)
+ base::MessageLoop::current()->QuitWhenIdle();
+ } else {
+ EXPECT_EQ(base::File::FILE_OK, status_);
+ status_ = status;
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+ }
+
+ private:
+ // For post-operation status.
+ base::File::Error status_;
+ int64_t bytes_written_;
+ FileWriterDelegate::WriteProgressStatus write_status_;
+};
+
+class BlobURLRequestJobFactory;
+
+} // namespace (anonymous)
+
+class FileWriterDelegateTest : public PlatformTest {
+ public:
+ FileWriterDelegateTest() {}
+
+ protected:
+ void SetUp() override;
+ void TearDown() override;
+
+ int64_t usage() {
+ return file_system_context_->GetQuotaUtil(kFileSystemType)
+ ->GetOriginUsageOnFileTaskRunner(
+ file_system_context_.get(), kOrigin, kFileSystemType);
+ }
+
+ int64_t GetFileSizeOnDisk(const char* test_file_path) {
+ // There might be in-flight flush/write.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(&base::DoNothing));
+ base::RunLoop().RunUntilIdle();
+
+ FileSystemURL url = GetFileSystemURL(test_file_path);
+ base::File::Info file_info;
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context_.get(), url, &file_info));
+ return file_info.size;
+ }
+
+ FileSystemURL GetFileSystemURL(const char* file_name) const {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name));
+ }
+
+ FileWriterDelegate* CreateWriterDelegate(const char* test_file_path,
+ int64_t offset,
+ int64_t allowed_growth) {
+ storage::SandboxFileStreamWriter* writer =
+ new storage::SandboxFileStreamWriter(
+ file_system_context_.get(),
+ GetFileSystemURL(test_file_path),
+ offset,
+ *file_system_context_->GetUpdateObservers(kFileSystemType));
+ writer->set_default_quota(allowed_growth);
+ return new FileWriterDelegate(
+ std::unique_ptr<storage::FileStreamWriter>(writer),
+ storage::FlushPolicy::FLUSH_ON_COMPLETION);
+ }
+
+ FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) {
+ return base::Bind(&Result::DidWrite, base::Unretained(result));
+ }
+
+ // Creates and sets up a FileWriterDelegate for writing the given |blob_url|,
+ // and creates a new FileWriterDelegate for the file.
+ void PrepareForWrite(const char* test_file_path,
+ const GURL& blob_url,
+ int64_t offset,
+ int64_t allowed_growth) {
+ file_writer_delegate_.reset(
+ CreateWriterDelegate(test_file_path, offset, allowed_growth));
+ request_ = empty_context_.CreateRequest(blob_url, net::DEFAULT_PRIORITY,
+ file_writer_delegate_.get(),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ }
+
+ // This should be alive until the very end of this instance.
+ base::MessageLoopForIO loop_;
+
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+
+ net::URLRequestContext empty_context_;
+ std::unique_ptr<FileWriterDelegate> file_writer_delegate_;
+ std::unique_ptr<net::URLRequest> request_;
+ std::unique_ptr<BlobURLRequestJobFactory> job_factory_;
+
+ base::ScopedTempDir dir_;
+
+ static const char* content_;
+};
+
+const char* FileWriterDelegateTest::content_ = NULL;
+
+namespace {
+
+static std::string g_content;
+
+class FileWriterDelegateTestJob : public net::URLRequestJob {
+ public:
+ FileWriterDelegateTestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& content)
+ : net::URLRequestJob(request, network_delegate),
+ content_(content),
+ remaining_bytes_(content.length()),
+ cursor_(0),
+ weak_factory_(this) {}
+
+ void Start() override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ int ReadRawData(net::IOBuffer* buf, int buf_size) override {
+ if (remaining_bytes_ < buf_size)
+ buf_size = remaining_bytes_;
+
+ for (int i = 0; i < buf_size; ++i)
+ buf->data()[i] = content_[cursor_++];
+ remaining_bytes_ -= buf_size;
+
+ return buf_size;
+ }
+
+ void GetResponseInfo(net::HttpResponseInfo* info) override {
+ const char kStatus[] = "HTTP/1.1 200 OK\0";
+ const size_t kStatusLen = arraysize(kStatus);
+
+ info->headers =
+ new net::HttpResponseHeaders(std::string(kStatus, kStatusLen));
+ }
+
+ protected:
+ ~FileWriterDelegateTestJob() override {}
+
+ private:
+ std::string content_;
+ int remaining_bytes_;
+ int cursor_;
+
+ base::WeakPtrFactory<FileWriterDelegateTestJob> weak_factory_;
+};
+
+class BlobURLRequestJobFactory : public net::URLRequestJobFactory {
+ public:
+ explicit BlobURLRequestJobFactory(const char** content_data)
+ : content_data_(content_data) {
+ }
+
+ net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return new FileWriterDelegateTestJob(
+ request, network_delegate, *content_data_);
+ }
+
+ net::URLRequestJob* MaybeInterceptRedirect(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const GURL& location) const override {
+ return nullptr;
+ }
+
+ net::URLRequestJob* MaybeInterceptResponse(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return nullptr;
+ }
+
+ bool IsHandledProtocol(const std::string& scheme) const override {
+ return scheme == "blob";
+ }
+
+ bool IsSafeRedirectTarget(const GURL& location) const override {
+ return true;
+ }
+
+ private:
+ const char** content_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJobFactory);
+};
+
+} // namespace (anonymous)
+
+void FileWriterDelegateTest::SetUp() {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, dir_.GetPath());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(),
+ GetFileSystemURL("test")));
+ job_factory_.reset(new BlobURLRequestJobFactory(&content_));
+ empty_context_.set_job_factory(job_factory_.get());
+}
+
+void FileWriterDelegateTest::TearDown() {
+ file_system_context_ = NULL;
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) {
+ const GURL kBlobURL("blob:nolimit");
+ content_ = kData;
+
+ PrepareForWrite("test", kBlobURL, 0, std::numeric_limits<int64_t>::max());
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_), GetWriteCallback(&result));
+ base::RunLoop().Run();
+
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) {
+ const GURL kBlobURL("blob:just");
+ content_ = kData;
+ const int64_t kAllowedGrowth = kDataSize;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_), GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+}
+
+TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) {
+ const GURL kBlobURL("blob:failure");
+ content_ = kData;
+ const int64_t kAllowedGrowth = kDataSize - 1;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_), GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status());
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) {
+ const GURL kBlobURL("blob:zero");
+ content_ = "";
+ int64_t kAllowedGrowth = 0;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_), GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) {
+ std::unique_ptr<FileWriterDelegate> file_writer_delegate2;
+ std::unique_ptr<net::URLRequest> request2;
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(),
+ GetFileSystemURL("test2")));
+
+ const GURL kBlobURL("blob:nolimitconcurrent");
+ const GURL kBlobURL2("blob:nolimitconcurrent2");
+ content_ = kData;
+
+ PrepareForWrite("test", kBlobURL, 0, std::numeric_limits<int64_t>::max());
+
+ // Credate another FileWriterDelegate for concurrent write.
+ file_writer_delegate2.reset(
+ CreateWriterDelegate("test2", 0, std::numeric_limits<int64_t>::max()));
+ request2 = empty_context_.CreateRequest(kBlobURL2, net::DEFAULT_PRIORITY,
+ file_writer_delegate2.get(),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ Result result, result2;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_), GetWriteCallback(&result));
+ file_writer_delegate2->Start(std::move(request2), GetWriteCallback(&result2));
+ base::RunLoop().Run();
+ if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING ||
+ result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING)
+ base::RunLoop().Run();
+
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status());
+ file_writer_delegate_.reset();
+ file_writer_delegate2.reset();
+
+ ASSERT_EQ(kDataSize * 2, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage());
+
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ EXPECT_EQ(kDataSize, result2.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result2.status());
+}
+
+TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) {
+ const GURL kBlobURL("blob:failure-with-updated-quota");
+ content_ = kData;
+
+ // Writing kDataSize (=45) bytes data while allowed_growth is 100.
+ int64_t offset = 0;
+ int64_t allowed_growth = 100;
+ ASSERT_LT(kDataSize, allowed_growth);
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(std::move(request_),
+ GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ }
+
+ // Trying to overwrite kDataSize bytes data while allowed_growth is 20.
+ offset = 0;
+ allowed_growth = 20;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(std::move(request_),
+ GetWriteCallback(&result));
+ base::RunLoop().Run();
+ EXPECT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ }
+
+ // Trying to write kDataSize bytes data from offset 25 while
+ // allowed_growth is 55.
+ offset = 25;
+ allowed_growth = 55;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(std::move(request_),
+ GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(offset + kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ }
+
+ // Trying to overwrite 45 bytes data while allowed_growth is -20.
+ offset = 0;
+ allowed_growth = -20;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+ int64_t pre_write_usage = GetFileSizeOnDisk("test");
+
+ {
+ Result result;
+ file_writer_delegate_->Start(std::move(request_),
+ GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(pre_write_usage, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_OK, result.status());
+ }
+
+ // Trying to overwrite 45 bytes data with offset pre_write_usage - 20,
+ // while allowed_growth is 10.
+ const int kOverlap = 20;
+ offset = pre_write_usage - kOverlap;
+ allowed_growth = 10;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(std::move(request_),
+ GetWriteCallback(&result));
+ base::RunLoop().Run();
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(pre_write_usage + allowed_growth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written());
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status());
+ }
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc b/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc
index d0c7c72007d..38b472d503a 100644
--- a/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc
+++ b/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc
@@ -16,6 +16,7 @@
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
diff --git a/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc b/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc
index ca798469091..265d9bd7feb 100644
--- a/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc
+++ b/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc
@@ -13,6 +13,7 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
diff --git a/chromium/storage/browser/fileapi/local_file_util_unittest.cc b/chromium/storage/browser/fileapi/local_file_util_unittest.cc
new file mode 100644
index 00000000000..7a3ff5029a1
--- /dev/null
+++ b/chromium/storage/browser/fileapi/local_file_util_unittest.cc
@@ -0,0 +1,382 @@
+// Copyright 2013 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/local_file_util.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::AsyncFileUtilAdapter;
+using storage::FileSystemContext;
+using storage::FileSystemOperationContext;
+using storage::FileSystemURL;
+using storage::LocalFileUtil;
+
+namespace content {
+
+namespace {
+
+const GURL kOrigin("http://foo/");
+const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest;
+
+} // namespace
+
+class LocalFileUtilTest : public testing::Test {
+ public:
+ LocalFileUtilTest() {}
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, data_dir_.GetPath());
+ }
+
+ void TearDown() override {
+ file_system_context_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ FileSystemOperationContext* NewContext() {
+ FileSystemOperationContext* context =
+ new FileSystemOperationContext(file_system_context_.get());
+ context->set_update_observers(
+ *file_system_context_->GetUpdateObservers(kFileSystemType));
+ return context;
+ }
+
+ LocalFileUtil* file_util() {
+ AsyncFileUtilAdapter* adapter = static_cast<AsyncFileUtilAdapter*>(
+ file_system_context_->GetAsyncFileUtil(kFileSystemType));
+ return static_cast<LocalFileUtil*>(adapter->sync_file_util());
+ }
+
+ FileSystemURL CreateURL(const std::string& file_name) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name));
+ }
+
+ base::FilePath LocalPath(const char *file_name) {
+ base::FilePath path;
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+ file_util()->GetLocalFilePath(context.get(), CreateURL(file_name), &path);
+ return path;
+ }
+
+ bool FileExists(const char *file_name) {
+ return base::PathExists(LocalPath(file_name)) &&
+ !base::DirectoryExists(LocalPath(file_name));
+ }
+
+ bool DirectoryExists(const char *file_name) {
+ return base::DirectoryExists(LocalPath(file_name));
+ }
+
+ int64_t GetSize(const char* file_name) {
+ base::File::Info info;
+ base::GetFileInfo(LocalPath(file_name), &info);
+ return info.size;
+ }
+
+ base::File CreateFile(const char* file_name) {
+ int file_flags = base::File::FLAG_CREATE |
+ base::File::FLAG_WRITE | base::File::FLAG_ASYNC;
+
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+ return file_util()->CreateOrOpen(context.get(), CreateURL(file_name),
+ file_flags);
+ }
+
+ base::File::Error EnsureFileExists(const char* file_name,
+ bool* created) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+ return file_util()->EnsureFileExists(context.get(),
+ CreateURL(file_name), created);
+ }
+
+ FileSystemContext* file_system_context() {
+ return file_system_context_.get();
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::ScopedTempDir data_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileUtilTest);
+};
+
+TEST_F(LocalFileUtilTest, CreateAndClose) {
+ const char *file_name = "test_file";
+ base::File file = CreateFile(file_name);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+}
+
+// base::CreateSymbolicLink is only supported on POSIX.
+#if defined(OS_POSIX)
+TEST_F(LocalFileUtilTest, CreateFailForSymlink) {
+ // Create symlink target file.
+ const char *target_name = "symlink_target";
+ base::File target_file = CreateFile(target_name);
+ ASSERT_TRUE(target_file.IsValid());
+ ASSERT_TRUE(target_file.created());
+ base::FilePath target_path = LocalPath(target_name);
+
+ // Create symlink where target must be real file.
+ const char *symlink_name = "symlink_file";
+ base::FilePath symlink_path = LocalPath(symlink_name);
+ ASSERT_TRUE(base::CreateSymbolicLink(target_path, symlink_path));
+ ASSERT_TRUE(FileExists(symlink_name));
+
+ // Try to open the symlink file which should fail.
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+ FileSystemURL url = CreateURL(symlink_name);
+ int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ base::File file = file_util()->CreateOrOpen(context.get(), url, file_flags);
+ ASSERT_FALSE(file.IsValid());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error_details());
+}
+#endif
+
+TEST_F(LocalFileUtilTest, EnsureFileExists) {
+ const char *file_name = "foobar";
+ bool created;
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
+ EXPECT_FALSE(created);
+}
+
+TEST_F(LocalFileUtilTest, TouchFile) {
+ const char *file_name = "test_file";
+ base::File file = CreateFile(file_name);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+
+ base::File::Info info;
+ ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info));
+ const base::Time new_accessed =
+ info.last_accessed + base::TimeDelta::FromHours(10);
+ const base::Time new_modified =
+ info.last_modified + base::TimeDelta::FromHours(5);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->Touch(context.get(), CreateURL(file_name),
+ new_accessed, new_modified));
+
+ ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info));
+ EXPECT_EQ(new_accessed, info.last_accessed);
+ EXPECT_EQ(new_modified, info.last_modified);
+}
+
+TEST_F(LocalFileUtilTest, TouchDirectory) {
+ const char *dir_name = "test_dir";
+ std::unique_ptr<FileSystemOperationContext> context(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->CreateDirectory(context.get(),
+ CreateURL(dir_name),
+ false /* exclusive */,
+ false /* recursive */));
+
+ base::File::Info info;
+ ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info));
+ const base::Time new_accessed =
+ info.last_accessed + base::TimeDelta::FromHours(10);
+ const base::Time new_modified =
+ info.last_modified + base::TimeDelta::FromHours(5);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->Touch(context.get(), CreateURL(dir_name),
+ new_accessed, new_modified));
+
+ ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info));
+ EXPECT_EQ(new_accessed, info.last_accessed);
+ EXPECT_EQ(new_modified, info.last_modified);
+}
+
+TEST_F(LocalFileUtilTest, Truncate) {
+ const char *file_name = "truncated";
+ bool created;
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(file_name), 1020));
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(1020, GetSize(file_name));
+}
+
+TEST_F(LocalFileUtilTest, CopyFile) {
+ const char *from_file = "fromfile";
+ const char *to_file1 = "tofile1";
+ const char *to_file2 = "tofile2";
+ bool created;
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ std::unique_ptr<FileSystemOperationContext> context;
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file1)));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file2)));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_TRUE(FileExists(to_file1));
+ EXPECT_EQ(1020, GetSize(to_file1));
+ EXPECT_TRUE(FileExists(to_file2));
+ EXPECT_EQ(1020, GetSize(to_file2));
+}
+
+TEST_F(LocalFileUtilTest, CopyDirectory) {
+ const char *from_dir = "fromdir";
+ const char *from_file = "fromdir/fromfile";
+ const char *to_dir = "todir";
+ const char *to_file = "todir/fromfile";
+ bool created;
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
+ false, false));
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_FALSE(DirectoryExists(to_dir));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_dir), CreateURL(to_dir)));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_TRUE(DirectoryExists(to_dir));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+TEST_F(LocalFileUtilTest, MoveFile) {
+ const char *from_file = "fromfile";
+ const char *to_file = "tofile";
+ bool created;
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Move(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file)));
+
+ EXPECT_FALSE(FileExists(from_file));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+TEST_F(LocalFileUtilTest, MoveDirectory) {
+ const char *from_dir = "fromdir";
+ const char *from_file = "fromdir/fromfile";
+ const char *to_dir = "todir";
+ const char *to_file = "todir/fromfile";
+ bool created;
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
+ false, false));
+ ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_FALSE(DirectoryExists(to_dir));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Move(file_system_context(),
+ CreateURL(from_dir),
+ CreateURL(to_dir)));
+
+ EXPECT_FALSE(DirectoryExists(from_dir));
+ EXPECT_TRUE(DirectoryExists(to_dir));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/obfuscated_file_util_unittest.cc b/chromium/storage/browser/fileapi/obfuscated_file_util_unittest.cc
new file mode 100644
index 00000000000..acae5e06d09
--- /dev/null
+++ b/chromium/storage/browser/fileapi/obfuscated_file_util_unittest.cc
@@ -0,0 +1,2584 @@
+// Copyright 2013 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 <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/sandbox_directory_database.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/fileapi_test_file_set.h"
+#include "storage/browser/test/mock_file_change_observer.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/sandbox_file_system_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/database/database_identifier.h"
+#include "storage/common/quota/quota_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemContext;
+using storage::FileSystemOperation;
+using storage::FileSystemOperationContext;
+using storage::FileSystemURL;
+using storage::ObfuscatedFileUtil;
+using storage::SandboxDirectoryDatabase;
+using storage::SandboxIsolatedOriginDatabase;
+using storage::kFileSystemTypeTemporary;
+using storage::kFileSystemTypePersistent;
+
+namespace content {
+
+namespace {
+
+bool FileExists(const base::FilePath& path) {
+ return base::PathExists(path) && !base::DirectoryExists(path);
+}
+
+int64_t GetSize(const base::FilePath& path) {
+ int64_t size;
+ EXPECT_TRUE(base::GetFileSize(path, &size));
+ return size;
+}
+
+// After a move, the dest exists and the source doesn't.
+// After a copy, both source and dest exist.
+struct CopyMoveTestCaseRecord {
+ bool is_copy_not_move;
+ const char source_path[64];
+ const char dest_path[64];
+ bool cause_overwrite;
+};
+
+const CopyMoveTestCaseRecord kCopyMoveTestCases[] = {
+ // This is the combinatoric set of:
+ // rename vs. same-name
+ // different directory vs. same directory
+ // overwrite vs. no-overwrite
+ // copy vs. move
+ // We can never be called with source and destination paths identical, so
+ // those cases are omitted.
+ {true, "dir0/file0", "dir0/file1", false},
+ {false, "dir0/file0", "dir0/file1", false},
+ {true, "dir0/file0", "dir0/file1", true},
+ {false, "dir0/file0", "dir0/file1", true},
+
+ {true, "dir0/file0", "dir1/file0", false},
+ {false, "dir0/file0", "dir1/file0", false},
+ {true, "dir0/file0", "dir1/file0", true},
+ {false, "dir0/file0", "dir1/file0", true},
+ {true, "dir0/file0", "dir1/file1", false},
+ {false, "dir0/file0", "dir1/file1", false},
+ {true, "dir0/file0", "dir1/file1", true},
+ {false, "dir0/file0", "dir1/file1", true},
+};
+
+struct OriginEnumerationTestRecord {
+ std::string origin_url;
+ bool has_temporary;
+ bool has_persistent;
+};
+
+const OriginEnumerationTestRecord kOriginEnumerationTestRecords[] = {
+ {"http://example.com/", false, true},
+ {"http://example1.com/", true, false},
+ {"https://example1.com/", true, true},
+ {"file:///", false, true},
+ {"http://example.com:8000/", false, true},
+};
+
+FileSystemURL FileSystemURLAppend(
+ const FileSystemURL& url, const base::FilePath::StringType& child) {
+ return FileSystemURL::CreateForTest(
+ url.origin(), url.mount_type(), url.virtual_path().Append(child));
+}
+
+FileSystemURL FileSystemURLAppendUTF8(
+ const FileSystemURL& url, const std::string& child) {
+ return FileSystemURL::CreateForTest(
+ url.origin(),
+ url.mount_type(),
+ url.virtual_path().Append(base::FilePath::FromUTF8Unsafe(child)));
+}
+
+FileSystemURL FileSystemURLDirName(const FileSystemURL& url) {
+ return FileSystemURL::CreateForTest(
+ url.origin(),
+ url.mount_type(),
+ storage::VirtualPath::DirName(url.virtual_path()));
+}
+
+std::string GetTypeString(storage::FileSystemType type) {
+ return storage::SandboxFileSystemBackendDelegate::GetTypeString(type);
+}
+
+bool HasFileSystemType(ObfuscatedFileUtil::AbstractOriginEnumerator* enumerator,
+ storage::FileSystemType type) {
+ return enumerator->HasTypeDirectory(GetTypeString(type));
+}
+
+} // namespace
+
+// TODO(ericu): The vast majority of this and the other FSFU subclass tests
+// could theoretically be shared. It would basically be a FSFU interface
+// compliance test, and only the subclass-specific bits that look into the
+// implementation would need to be written per-subclass.
+class ObfuscatedFileUtilTest : public testing::Test {
+ public:
+ ObfuscatedFileUtilTest()
+ : origin_(GURL("http://www.example.com")),
+ type_(storage::kFileSystemTypeTemporary),
+ sandbox_file_system_(origin_, type_),
+ quota_status_(storage::kQuotaStatusUnknown),
+ usage_(-1),
+ weak_factory_(this) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+
+ storage_policy_ = new MockSpecialStoragePolicy();
+
+ quota_manager_ = new storage::QuotaManager(
+ false /* is_incognito */, data_dir_.GetPath(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(), storage_policy_.get(),
+ storage::GetQuotaSettingsFunc());
+ storage::QuotaSettings settings;
+ settings.per_host_quota = 25 * 1024 * 1024;
+ settings.pool_size = settings.per_host_quota * 5;
+ settings.must_remain_available = 10 * 1024 * 1024;
+ settings.refresh_interval = base::TimeDelta::Max();
+ quota_manager_->SetQuotaSettings(settings);
+
+ // Every time we create a new sandbox_file_system helper,
+ // it creates another context, which creates another path manager,
+ // another sandbox_backend, and another OFU.
+ // We need to pass in the context to skip all that.
+ file_system_context_ = CreateFileSystemContextForTesting(
+ quota_manager_->proxy(), data_dir_.GetPath());
+
+ sandbox_file_system_.SetUp(file_system_context_.get());
+
+ change_observers_ =
+ storage::MockFileChangeObserver::CreateList(&change_observer_);
+ }
+
+ void TearDown() override {
+ quota_manager_ = NULL;
+ sandbox_file_system_.TearDown();
+ }
+
+ std::unique_ptr<FileSystemOperationContext> LimitedContext(
+ int64_t allowed_bytes_growth) {
+ std::unique_ptr<FileSystemOperationContext> context(
+ sandbox_file_system_.NewOperationContext());
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ return context;
+ }
+
+ std::unique_ptr<FileSystemOperationContext> UnlimitedContext() {
+ return LimitedContext(std::numeric_limits<int64_t>::max());
+ }
+
+ FileSystemOperationContext* NewContext(
+ SandboxFileSystemTestHelper* file_system) {
+ change_observer()->ResetCount();
+ FileSystemOperationContext* context;
+ if (file_system)
+ context = file_system->NewOperationContext();
+ else
+ context = sandbox_file_system_.NewOperationContext();
+ // Setting allowed_bytes_growth big enough for all tests.
+ context->set_allowed_bytes_growth(1024 * 1024);
+ context->set_change_observers(change_observers());
+ return context;
+ }
+
+ const storage::ChangeObserverList& change_observers() const {
+ return change_observers_;
+ }
+
+ storage::MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ // This can only be used after SetUp has run and created file_system_context_
+ // and obfuscated_file_util_.
+ // Use this for tests which need to run in multiple origins; we need a test
+ // helper per origin.
+ SandboxFileSystemTestHelper* NewFileSystem(const GURL& origin,
+ storage::FileSystemType type) {
+ SandboxFileSystemTestHelper* file_system =
+ new SandboxFileSystemTestHelper(origin, type);
+
+ file_system->SetUp(file_system_context_.get());
+ return file_system;
+ }
+
+ std::unique_ptr<ObfuscatedFileUtil> CreateObfuscatedFileUtil(
+ storage::SpecialStoragePolicy* storage_policy) {
+ return std::unique_ptr<ObfuscatedFileUtil>(
+ ObfuscatedFileUtil::CreateForTesting(
+ storage_policy, data_dir_path(), NULL,
+ base::ThreadTaskRunnerHandle::Get().get()));
+ }
+
+ ObfuscatedFileUtil* ofu() {
+ return static_cast<ObfuscatedFileUtil*>(sandbox_file_system_.file_util());
+ }
+
+ const base::FilePath& test_directory() const { return data_dir_.GetPath(); }
+
+ const GURL& origin() const {
+ return origin_;
+ }
+
+ storage::FileSystemType type() const { return type_; }
+
+ std::string type_string() const {
+ return GetTypeString(type_);
+ }
+
+ int64_t ComputeTotalFileSize() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ void GetUsageFromQuotaManager() {
+ int64_t quota = -1;
+ quota_status_ =
+ AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
+ origin(),
+ sandbox_file_system_.type(),
+ &usage_,
+ &quota);
+ EXPECT_EQ(storage::kQuotaStatusOk, quota_status_);
+ }
+
+ void RevokeUsageCache() {
+ quota_manager_->ResetUsageTracker(sandbox_file_system_.storage_type());
+ usage_cache()->Delete(sandbox_file_system_.GetUsageCachePath());
+ }
+
+ int64_t SizeByQuotaUtil() {
+ return sandbox_file_system_.GetCachedOriginUsage();
+ }
+
+ int64_t SizeInUsageFile() {
+ base::RunLoop().RunUntilIdle();
+ int64_t usage = 0;
+ return usage_cache()->GetUsage(
+ sandbox_file_system_.GetUsageCachePath(), &usage) ? usage : -1;
+ }
+
+ bool PathExists(const FileSystemURL& url) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::File::Info file_info;
+ base::FilePath platform_path;
+ base::File::Error error = ofu()->GetFileInfo(
+ context.get(), url, &file_info, &platform_path);
+ return error == base::File::FILE_OK;
+ }
+
+ bool DirectoryExists(const FileSystemURL& url) {
+ return AsyncFileTestHelper::DirectoryExists(file_system_context(), url);
+ }
+
+ int64_t usage() const { return usage_; }
+ storage::FileSystemUsageCache* usage_cache() {
+ return sandbox_file_system_.usage_cache();
+ }
+
+ FileSystemURL CreateURLFromUTF8(const std::string& path) {
+ return sandbox_file_system_.CreateURLFromUTF8(path);
+ }
+
+ int64_t PathCost(const FileSystemURL& url) {
+ return ObfuscatedFileUtil::ComputeFilePathCost(url.path());
+ }
+
+ FileSystemURL CreateURL(const base::FilePath& path) {
+ return sandbox_file_system_.CreateURL(path);
+ }
+
+ void CheckFileAndCloseHandle(const FileSystemURL& url, base::File file) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url, &local_path));
+
+ base::File::Info file_info0;
+ base::FilePath data_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info0, &data_path));
+ EXPECT_EQ(data_path, local_path);
+ EXPECT_TRUE(FileExists(data_path));
+ EXPECT_EQ(0, GetSize(data_path));
+
+ const char data[] = "test data";
+ const int length = arraysize(data) - 1;
+
+ if (!file.IsValid()) {
+ file.Initialize(data_path,
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ EXPECT_FALSE(file.created());
+ }
+ ASSERT_EQ(length, file.Write(0, data, length));
+ file.Close();
+
+ base::File::Info file_info1;
+ EXPECT_EQ(length, GetSize(data_path));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info1, &data_path));
+ EXPECT_EQ(data_path, local_path);
+
+ EXPECT_FALSE(file_info0.is_directory);
+ EXPECT_FALSE(file_info1.is_directory);
+ EXPECT_FALSE(file_info0.is_symbolic_link);
+ EXPECT_FALSE(file_info1.is_symbolic_link);
+ EXPECT_EQ(0, file_info0.size);
+ EXPECT_EQ(length, file_info1.size);
+ EXPECT_LE(file_info0.last_modified, file_info1.last_modified);
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), url, length * 2));
+ EXPECT_EQ(length * 2, GetSize(data_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), url, 0));
+ EXPECT_EQ(0, GetSize(data_path));
+ }
+
+ void ValidateTestDirectory(
+ const FileSystemURL& root_url,
+ const std::set<base::FilePath::StringType>& files,
+ const std::set<base::FilePath::StringType>& directories) {
+ std::unique_ptr<FileSystemOperationContext> context;
+ std::set<base::FilePath::StringType>::const_iterator iter;
+ for (iter = files.begin(); iter != files.end(); ++iter) {
+ bool created = true;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(),
+ FileSystemURLAppend(root_url, *iter),
+ &created));
+ ASSERT_FALSE(created);
+ }
+ for (iter = directories.begin(); iter != directories.end(); ++iter) {
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(DirectoryExists(
+ FileSystemURLAppend(root_url, *iter)));
+ }
+ }
+
+ class UsageVerifyHelper {
+ public:
+ UsageVerifyHelper(std::unique_ptr<FileSystemOperationContext> context,
+ SandboxFileSystemTestHelper* file_system,
+ int64_t expected_usage)
+ : context_(std::move(context)),
+ sandbox_file_system_(file_system),
+ expected_usage_(expected_usage) {}
+
+ ~UsageVerifyHelper() {
+ base::RunLoop().RunUntilIdle();
+ Check();
+ }
+
+ FileSystemOperationContext* context() {
+ return context_.get();
+ }
+
+ private:
+ void Check() {
+ ASSERT_EQ(expected_usage_,
+ sandbox_file_system_->GetCachedOriginUsage());
+ }
+
+ std::unique_ptr<FileSystemOperationContext> context_;
+ SandboxFileSystemTestHelper* sandbox_file_system_;
+ int64_t expected_usage_;
+ };
+
+ std::unique_ptr<UsageVerifyHelper> AllowUsageIncrease(
+ int64_t requested_growth) {
+ int64_t usage = sandbox_file_system_.GetCachedOriginUsage();
+ return std::unique_ptr<UsageVerifyHelper>(
+ new UsageVerifyHelper(LimitedContext(requested_growth),
+ &sandbox_file_system_, usage + requested_growth));
+ }
+
+ std::unique_ptr<UsageVerifyHelper> DisallowUsageIncrease(
+ int64_t requested_growth) {
+ int64_t usage = sandbox_file_system_.GetCachedOriginUsage();
+ return std::unique_ptr<UsageVerifyHelper>(new UsageVerifyHelper(
+ LimitedContext(requested_growth - 1), &sandbox_file_system_, usage));
+ }
+
+ void FillTestDirectory(
+ const FileSystemURL& root_url,
+ std::set<base::FilePath::StringType>* files,
+ std::set<base::FilePath::StringType>* directories) {
+ std::unique_ptr<FileSystemOperationContext> context;
+ std::vector<storage::DirectoryEntry> entries;
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(),
+ root_url, &entries));
+ EXPECT_EQ(0UL, entries.size());
+
+ files->clear();
+ files->insert(FILE_PATH_LITERAL("first"));
+ files->insert(FILE_PATH_LITERAL("second"));
+ files->insert(FILE_PATH_LITERAL("third"));
+ directories->clear();
+ directories->insert(FILE_PATH_LITERAL("fourth"));
+ directories->insert(FILE_PATH_LITERAL("fifth"));
+ directories->insert(FILE_PATH_LITERAL("sixth"));
+ std::set<base::FilePath::StringType>::iterator iter;
+ for (iter = files->begin(); iter != files->end(); ++iter) {
+ bool created = false;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(),
+ FileSystemURLAppend(root_url, *iter),
+ &created));
+ ASSERT_TRUE(created);
+ }
+ for (iter = directories->begin(); iter != directories->end(); ++iter) {
+ bool exclusive = true;
+ bool recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(),
+ FileSystemURLAppend(root_url, *iter),
+ exclusive, recursive));
+ }
+ ValidateTestDirectory(root_url, *files, *directories);
+ }
+
+ void TestReadDirectoryHelper(const FileSystemURL& root_url) {
+ std::set<base::FilePath::StringType> files;
+ std::set<base::FilePath::StringType> directories;
+ FillTestDirectory(root_url, &files, &directories);
+
+ std::unique_ptr<FileSystemOperationContext> context;
+ std::vector<storage::DirectoryEntry> entries;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), root_url, &entries));
+ std::vector<storage::DirectoryEntry>::iterator entry_iter;
+ EXPECT_EQ(files.size() + directories.size(), entries.size());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ for (entry_iter = entries.begin(); entry_iter != entries.end();
+ ++entry_iter) {
+ const storage::DirectoryEntry& entry = *entry_iter;
+ std::set<base::FilePath::StringType>::iterator iter =
+ files.find(entry.name);
+ if (iter != files.end()) {
+ EXPECT_FALSE(entry.is_directory);
+ files.erase(iter);
+ continue;
+ }
+ iter = directories.find(entry.name);
+ EXPECT_FALSE(directories.end() == iter);
+ EXPECT_TRUE(entry.is_directory);
+ directories.erase(iter);
+ }
+ }
+
+ void TestTouchHelper(const FileSystemURL& url, bool is_file) {
+ base::Time last_access_time = base::Time::Now();
+ base::Time last_modified_time = base::Time::Now();
+
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Touch(context.get(), url, last_access_time,
+ last_modified_time));
+ // Currently we fire no change notifications for Touch.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ base::FilePath local_path;
+ base::File::Info file_info;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info, &local_path));
+ // We compare as time_t here to lower our resolution, to avoid false
+ // negatives caused by conversion to the local filesystem's native
+ // representation and back.
+ EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());
+
+ context.reset(NewContext(NULL));
+ last_modified_time += base::TimeDelta::FromHours(1);
+ last_access_time += base::TimeDelta::FromHours(14);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Touch(context.get(), url, last_access_time,
+ last_modified_time));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
+ EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());
+ if (is_file) // Directories in OFU don't support atime.
+ EXPECT_EQ(file_info.last_accessed.ToTimeT(), last_access_time.ToTimeT());
+ }
+
+ void TestCopyInForeignFileHelper(bool overwrite) {
+ base::ScopedTempDir source_dir;
+ ASSERT_TRUE(source_dir.CreateUniqueTempDir());
+ base::FilePath root_file_path = source_dir.GetPath();
+ base::FilePath src_file_path = root_file_path.AppendASCII("file_name");
+ FileSystemURL dest_url = CreateURLFromUTF8("new file");
+ int64_t src_file_length = 87;
+
+ base::File file(src_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ EXPECT_TRUE(file.created());
+ ASSERT_TRUE(file.SetLength(src_file_length));
+ file.Close();
+
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ if (overwrite) {
+ context.reset(NewContext(NULL));
+ bool created = false;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), dest_url, &created));
+ EXPECT_TRUE(created);
+
+ // We must have observed one (and only one) create_file_count.
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ }
+
+ const int64_t path_cost =
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path());
+ if (!overwrite) {
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(path_cost + src_file_length - 1);
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->CopyInForeignFile(context.get(),
+ src_file_path, dest_url));
+ }
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(path_cost + src_file_length);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyInForeignFile(context.get(),
+ src_file_path, dest_url));
+
+ EXPECT_TRUE(PathExists(dest_url));
+ EXPECT_FALSE(DirectoryExists(dest_url));
+
+ context.reset(NewContext(NULL));
+ base::File::Info file_info;
+ base::FilePath data_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), dest_url, &file_info,
+ &data_path));
+ EXPECT_NE(data_path, src_file_path);
+ EXPECT_TRUE(FileExists(data_path));
+ EXPECT_EQ(src_file_length, GetSize(data_path));
+
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(context.get(), dest_url));
+ }
+
+ void ClearTimestamp(const FileSystemURL& url) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Touch(context.get(), url, base::Time(), base::Time()));
+ EXPECT_EQ(base::Time(), GetModifiedTime(url));
+ }
+
+ base::Time GetModifiedTime(const FileSystemURL& url) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::FilePath data_path;
+ base::File::Info file_info;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info, &data_path));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ return file_info.last_modified;
+ }
+
+ void TestDirectoryTimestampHelper(const FileSystemURL& base_dir,
+ bool copy,
+ bool overwrite) {
+ std::unique_ptr<FileSystemOperationContext> context;
+ const FileSystemURL src_dir_url(
+ FileSystemURLAppendUTF8(base_dir, "foo_dir"));
+ const FileSystemURL dest_dir_url(
+ FileSystemURLAppendUTF8(base_dir, "bar_dir"));
+
+ const FileSystemURL src_file_url(
+ FileSystemURLAppendUTF8(src_dir_url, "hoge"));
+ const FileSystemURL dest_file_url(
+ FileSystemURLAppendUTF8(dest_dir_url, "fuga"));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), src_dir_url, true, true));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), dest_dir_url, true, true));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_file_url, &created));
+ if (overwrite) {
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(),
+ dest_file_url, &created));
+ }
+
+ ClearTimestamp(src_dir_url);
+ ClearTimestamp(dest_dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(),
+ src_file_url, dest_file_url,
+ FileSystemOperation::OPTION_NONE,
+ copy));
+ if (copy)
+ EXPECT_EQ(base::Time(), GetModifiedTime(src_dir_url));
+ else
+ EXPECT_NE(base::Time(), GetModifiedTime(src_dir_url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dest_dir_url));
+ }
+
+ void MaybeDropDatabasesAliveCaseTestBody() {
+ std::unique_ptr<ObfuscatedFileUtil> file_util =
+ CreateObfuscatedFileUtil(NULL);
+ file_util->InitOriginDatabase(GURL(), true /*create*/);
+ ASSERT_TRUE(file_util->origin_database_ != NULL);
+
+ // Callback to Drop DB is called while ObfuscatedFileUtilTest is
+ // still alive.
+ file_util->db_flush_delay_seconds_ = 0;
+ file_util->MarkUsed();
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_TRUE(file_util->origin_database_ == NULL);
+ }
+
+ void MaybeDropDatabasesAlreadyDeletedCaseTestBody() {
+ // Run message loop after OFU is already deleted to make sure callback
+ // doesn't cause a crash for use after free.
+ {
+ std::unique_ptr<ObfuscatedFileUtil> file_util =
+ CreateObfuscatedFileUtil(NULL);
+ file_util->InitOriginDatabase(GURL(), true /*create*/);
+ file_util->db_flush_delay_seconds_ = 0;
+ file_util->MarkUsed();
+ }
+
+ // At this point the callback is still in the message queue but OFU is gone.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void DestroyDirectoryDatabase_IsolatedTestBody() {
+ storage_policy_->AddIsolated(origin_);
+ std::unique_ptr<ObfuscatedFileUtil> file_util =
+ CreateObfuscatedFileUtil(storage_policy_.get());
+ const FileSystemURL url = FileSystemURL::CreateForTest(
+ origin_, kFileSystemTypePersistent, base::FilePath());
+
+ // Create DirectoryDatabase for isolated origin.
+ SandboxDirectoryDatabase* db =
+ file_util->GetDirectoryDatabase(url, true /* create */);
+ ASSERT_TRUE(db != NULL);
+
+ // Destory it.
+ file_util->DestroyDirectoryDatabase(
+ url.origin(), GetTypeString(url.type()));
+ ASSERT_TRUE(file_util->directories_.empty());
+ }
+
+ void GetDirectoryDatabase_IsolatedTestBody() {
+ storage_policy_->AddIsolated(origin_);
+ std::unique_ptr<ObfuscatedFileUtil> file_util =
+ CreateObfuscatedFileUtil(storage_policy_.get());
+ const FileSystemURL url = FileSystemURL::CreateForTest(
+ origin_, kFileSystemTypePersistent, base::FilePath());
+
+ // Create DirectoryDatabase for isolated origin.
+ SandboxDirectoryDatabase* db =
+ file_util->GetDirectoryDatabase(url, true /* create */);
+ ASSERT_TRUE(db != NULL);
+ ASSERT_EQ(1U, file_util->directories_.size());
+
+ // Remove isolated.
+ storage_policy_->RemoveIsolated(url.origin());
+
+ // This should still get the same database.
+ SandboxDirectoryDatabase* db2 =
+ file_util->GetDirectoryDatabase(url, false /* create */);
+ ASSERT_EQ(db, db2);
+ }
+
+ void MigrationBackFromIsolatedTestBody() {
+ std::string kFakeDirectoryData("0123456789");
+ base::FilePath old_directory_db_path;
+
+ // Initialize the directory with one origin using
+ // SandboxIsolatedOriginDatabase.
+ {
+ std::string origin_string = storage::GetIdentifierFromOrigin(origin_);
+ SandboxIsolatedOriginDatabase database_old(
+ origin_string, data_dir_path(),
+ base::FilePath(
+ SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory));
+ base::FilePath path;
+ EXPECT_TRUE(database_old.GetPathForOrigin(origin_string, &path));
+ EXPECT_FALSE(path.empty());
+
+ // Populate the origin directory with some fake data.
+ old_directory_db_path = data_dir_path().Append(path);
+ ASSERT_TRUE(base::CreateDirectory(old_directory_db_path));
+ EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()),
+ base::WriteFile(old_directory_db_path.AppendASCII("dummy"),
+ kFakeDirectoryData.data(),
+ kFakeDirectoryData.size()));
+ }
+
+ storage_policy_->AddIsolated(origin_);
+ std::unique_ptr<ObfuscatedFileUtil> file_util =
+ CreateObfuscatedFileUtil(storage_policy_.get());
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ base::FilePath origin_directory = file_util->GetDirectoryForOrigin(
+ origin_, true /* create */, &error);
+ EXPECT_EQ(base::File::FILE_OK, error);
+
+ // The database is migrated from the old one.
+ EXPECT_TRUE(base::DirectoryExists(origin_directory));
+ EXPECT_FALSE(base::DirectoryExists(old_directory_db_path));
+
+ // Check we see the same contents in the new origin directory.
+ std::string origin_db_data;
+ EXPECT_TRUE(base::PathExists(origin_directory.AppendASCII("dummy")));
+ EXPECT_TRUE(base::ReadFileToString(
+ origin_directory.AppendASCII("dummy"), &origin_db_data));
+ EXPECT_EQ(kFakeDirectoryData, origin_db_data);
+ }
+
+ int64_t ComputeCurrentUsage() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ FileSystemContext* file_system_context() {
+ return sandbox_file_system_.file_system_context();
+ }
+
+ const base::FilePath& data_dir_path() const { return data_dir_.GetPath(); }
+
+ protected:
+ base::ScopedTempDir data_dir_;
+ base::MessageLoopForIO message_loop_;
+ scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
+ scoped_refptr<storage::QuotaManager> quota_manager_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ GURL origin_;
+ storage::FileSystemType type_;
+ SandboxFileSystemTestHelper sandbox_file_system_;
+ storage::QuotaStatusCode quota_status_;
+ int64_t usage_;
+ storage::MockFileChangeObserver change_observer_;
+ storage::ChangeObserverList change_observers_;
+ base::WeakPtrFactory<ObfuscatedFileUtilTest> weak_factory_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest);
+};
+
+TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) {
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
+
+ base::File file = ofu()->CreateOrOpen(context.get(), url, file_flags);
+ EXPECT_FALSE(file.IsValid());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error_details());
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteFile(context.get(), url));
+
+ url = CreateURLFromUTF8("test file");
+
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ file = ofu()->CreateOrOpen(context.get(), url, file_flags);
+ EXPECT_FALSE(file.IsValid());
+ ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE, file.error_details());
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ file = ofu()->CreateOrOpen(context.get(), url, file_flags);
+ EXPECT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ CheckFileAndCloseHandle(url, std::move(file));
+
+ context.reset(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url, &local_path));
+ EXPECT_TRUE(base::PathExists(local_path));
+
+ // Verify that deleting a file isn't stopped by zero quota, and that it frees
+ // up quote from its path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_FALSE(base::PathExists(local_path));
+ EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
+ context->allowed_bytes_growth());
+
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ FileSystemURL directory_url = CreateURLFromUTF8(
+ "series/of/directories");
+ url = FileSystemURLAppendUTF8(directory_url, "file name");
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), directory_url, exclusive,
+ recursive));
+ // The oepration created 3 directories recursively.
+ EXPECT_EQ(3, change_observer()->get_and_reset_create_directory_count());
+
+ context.reset(NewContext(NULL));
+ file = ofu()->CreateOrOpen(context.get(), url, file_flags);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ CheckFileAndCloseHandle(url, std::move(file));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url, &local_path));
+ EXPECT_TRUE(base::PathExists(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_FALSE(base::PathExists(local_path));
+
+ // Make sure we have no unexpected changes.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestTruncate) {
+ bool created = false;
+ FileSystemURL url = CreateURLFromUTF8("file");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->Truncate(context.get(), url, 4));
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ context.reset(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url, &local_path));
+ EXPECT_EQ(0, GetSize(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK, ofu()->Truncate(context.get(), url, 10));
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_EQ(10, GetSize(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK, ofu()->Truncate(context.get(), url, 1));
+ EXPECT_EQ(1, GetSize(local_path));
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ // Make sure we have no unexpected changes.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation) {
+ bool created = false;
+ FileSystemURL url = CreateURLFromUTF8("file");
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(url))->context(),
+ url, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(AllowUsageIncrease(1020)->context(), url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(AllowUsageIncrease(-1020)->context(), url, 0));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->Truncate(DisallowUsageIncrease(1021)->context(),
+ url, 1021));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(AllowUsageIncrease(1020)->context(), url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(AllowUsageIncrease(0)->context(), url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ // quota exceeded
+ {
+ std::unique_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(-1);
+ helper->context()->set_allowed_bytes_growth(
+ helper->context()->allowed_bytes_growth() - 1);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(helper->context(), url, 1019));
+ ASSERT_EQ(1019, ComputeTotalFileSize());
+ }
+
+ // Delete backing file to make following truncation fail.
+ base::FilePath local_path;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(UnlimitedContext().get(), url,
+ &local_path));
+ ASSERT_FALSE(local_path.empty());
+ ASSERT_TRUE(base::DeleteFile(local_path, false));
+
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->Truncate(LimitedContext(1234).get(), url, 1234));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists) {
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ bool created = false;
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ url = CreateURLFromUTF8("test file");
+ created = false;
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_FALSE(created);
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ CheckFileAndCloseHandle(url, base::File());
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_FALSE(created);
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Also test in a subdirectory.
+ url = CreateURLFromUTF8("path/to/file.txt");
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), FileSystemURLDirName(url),
+ exclusive, recursive));
+ // 2 directories: path/ and path/to.
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool exclusive = false;
+ bool recursive = false;
+ FileSystemURL url = CreateURLFromUTF8("foo/bar");
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteDirectory(context.get(), url));
+
+ FileSystemURL root = CreateURLFromUTF8(std::string());
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), root));
+
+ context.reset(NewContext(NULL));
+ exclusive = false;
+ recursive = true;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ context.reset(NewContext(NULL));
+ EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), root));
+ EXPECT_TRUE(DirectoryExists(FileSystemURLDirName(url)));
+
+ context.reset(NewContext(NULL));
+ EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(),
+ FileSystemURLDirName(url)));
+
+ // Can't remove a non-empty directory.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
+ ofu()->DeleteDirectory(context.get(),
+ FileSystemURLDirName(url)));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ base::File::Info file_info;
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
+ EXPECT_TRUE(local_path.empty());
+ EXPECT_TRUE(file_info.is_directory);
+ EXPECT_FALSE(file_info.is_symbolic_link);
+
+ // Same create again should succeed, since exclusive is false.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ exclusive = true;
+ recursive = true;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that deleting a directory isn't stopped by zero quota, and that it
+ // frees up quota from its path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
+ context->allowed_bytes_growth());
+
+ url = CreateURLFromUTF8("foo/bop");
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
+
+ // Verify that file creation requires sufficient quota for the path.
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ exclusive = true;
+ recursive = false;
+ url = CreateURLFromUTF8("foo");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ url = CreateURLFromUTF8("blah");
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadDirectory) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ FileSystemURL url = CreateURLFromUTF8("directory/to/use");
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ TestReadDirectoryHelper(url);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash) {
+ TestReadDirectoryHelper(CreateURLFromUTF8(std::string()));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) {
+ TestReadDirectoryHelper(CreateURLFromUTF8("/"));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) {
+ FileSystemURL url = CreateURLFromUTF8("file");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+
+ std::vector<storage::DirectoryEntry> entries;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(), url,
+ &entries));
+
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestTouch) {
+ FileSystemURL url = CreateURLFromUTF8("file");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ base::Time last_access_time = base::Time::Now();
+ base::Time last_modified_time = base::Time::Now();
+
+ // It's not there yet.
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->Touch(context.get(), url, last_access_time,
+ last_modified_time));
+
+ // OK, now create it.
+ context.reset(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ TestTouchHelper(url, true);
+
+ // Now test a directory:
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = false;
+ url = CreateURLFromUTF8("dir");
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ TestTouchHelper(url, false);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestPathQuotas) {
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ url = CreateURLFromUTF8("file name");
+ context->set_allowed_bytes_growth(5);
+ bool created = false;
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_FALSE(created);
+ context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+ int64_t path_cost = ObfuscatedFileUtil::ComputeFilePathCost(url.path());
+ EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
+
+ context->set_allowed_bytes_growth(1024);
+ bool exclusive = true;
+ bool recursive = true;
+ url = CreateURLFromUTF8("directory/to/use");
+ std::vector<base::FilePath::StringType> components;
+ url.path().GetComponents(&components);
+ path_cost = 0;
+ typedef std::vector<base::FilePath::StringType>::iterator iterator;
+ for (iterator iter = components.begin();
+ iter != components.end(); ++iter) {
+ path_cost += ObfuscatedFileUtil::ComputeFilePathCost(
+ base::FilePath(*iter));
+ }
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) {
+ FileSystemURL source_url = CreateURLFromUTF8("path0.txt");
+ FileSystemURL dest_url = CreateURLFromUTF8("path1.txt");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool is_copy_not_move = false;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ FileSystemOperation::OPTION_NONE,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ is_copy_not_move = true;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ FileSystemOperation::OPTION_NONE,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ source_url = CreateURLFromUTF8("dir/dir/file");
+ bool exclusive = true;
+ bool recursive = true;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(),
+ FileSystemURLDirName(source_url),
+ exclusive, recursive));
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ is_copy_not_move = false;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ FileSystemOperation::OPTION_NONE,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ is_copy_not_move = true;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ FileSystemOperation::OPTION_NONE,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) {
+ const int64_t kSourceLength = 5;
+ const int64_t kDestLength = 50;
+
+ for (size_t i = 0; i < arraysize(kCopyMoveTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i);
+ const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i];
+ SCOPED_TRACE(testing::Message() << "\t is_copy_not_move " <<
+ test_case.is_copy_not_move);
+ SCOPED_TRACE(testing::Message() << "\t source_path " <<
+ test_case.source_path);
+ SCOPED_TRACE(testing::Message() << "\t dest_path " <<
+ test_case.dest_path);
+ SCOPED_TRACE(testing::Message() << "\t cause_overwrite " <<
+ test_case.cause_overwrite);
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool exclusive = false;
+ bool recursive = true;
+ FileSystemURL source_url = CreateURLFromUTF8(test_case.source_path);
+ FileSystemURL dest_url = CreateURLFromUTF8(test_case.dest_path);
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(),
+ FileSystemURLDirName(source_url),
+ exclusive, recursive));
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(),
+ FileSystemURLDirName(dest_url),
+ exclusive, recursive));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), source_url, &created));
+ ASSERT_TRUE(created);
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), source_url, kSourceLength));
+
+ if (test_case.cause_overwrite) {
+ context.reset(NewContext(NULL));
+ created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), dest_url, &created));
+ ASSERT_TRUE(created);
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), dest_url, kDestLength));
+ }
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ FileSystemOperation::OPTION_NONE,
+ test_case.is_copy_not_move));
+
+ if (test_case.is_copy_not_move) {
+ base::File::Info file_info;
+ base::FilePath local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), source_url, &file_info,
+ &local_path));
+ EXPECT_EQ(kSourceLength, file_info.size);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(context.get(), source_url));
+ } else {
+ base::File::Info file_info;
+ base::FilePath local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->GetFileInfo(context.get(), source_url, &file_info,
+ &local_path));
+ }
+ base::File::Info file_info;
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(), dest_url, &file_info,
+ &local_path));
+ EXPECT_EQ(kSourceLength, file_info.size);
+
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(context.get(), dest_url));
+ }
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ FileSystemURL dest_url = CreateURLFromUTF8("destination path");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_url, &created));
+
+ bool is_copy = true;
+ // Copy, no overwrite.
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - 1);
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+
+ // Copy, with overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ FileSystemURL dest_url = CreateURLFromUTF8("destination path");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_url, &created));
+
+ bool is_copy = false;
+ // Move, rename, no overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) -
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()) - 1);
+ EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) -
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_url, &created));
+
+ // Move, rename, with overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_url, &created));
+
+ bool exclusive = true;
+ bool recursive = false;
+ FileSystemURL dir_url = CreateURLFromUTF8("directory path");
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir_url, exclusive,
+ recursive));
+
+ FileSystemURL dest_url = FileSystemURLAppend(
+ dir_url, src_url.path().value());
+
+ bool is_copy = false;
+ int64_t allowed_bytes_growth = -1000; // Over quota, this should still work.
+ // Move, no rename, no overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+ EXPECT_EQ(allowed_bytes_growth, context->allowed_bytes_growth());
+
+ // Move, no rename, with overwrite.
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_url, &created));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
+ FileSystemOperation::OPTION_NONE, is_copy));
+ EXPECT_EQ(
+ allowed_bytes_growth +
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()),
+ context->allowed_bytes_growth());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile) {
+ TestCopyInForeignFileHelper(false /* overwrite */);
+ TestCopyInForeignFileHelper(true /* overwrite */);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestEnumerator) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ FileSystemURL src_url = CreateURLFromUTF8("source dir");
+ bool exclusive = true;
+ bool recursive = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), src_url, exclusive,
+ recursive));
+
+ std::set<base::FilePath::StringType> files;
+ std::set<base::FilePath::StringType> directories;
+ FillTestDirectory(src_url, &files, &directories);
+
+ FileSystemURL dest_url = CreateURLFromUTF8("destination dir");
+
+ EXPECT_FALSE(DirectoryExists(dest_url));
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Copy(
+ file_system_context(), src_url, dest_url));
+
+ ValidateTestDirectory(dest_url, files, directories);
+ EXPECT_TRUE(DirectoryExists(src_url));
+ EXPECT_TRUE(DirectoryExists(dest_url));
+ recursive = true;
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Remove(
+ file_system_context(), dest_url, recursive));
+ EXPECT_FALSE(DirectoryExists(dest_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator) {
+ std::unique_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator(
+ ofu()->CreateOriginEnumerator());
+ // The test helper starts out with a single filesystem.
+ EXPECT_TRUE(enumerator.get());
+ EXPECT_EQ(origin(), enumerator->Next());
+ ASSERT_TRUE(type() == kFileSystemTypeTemporary);
+ EXPECT_TRUE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
+ EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));
+ EXPECT_EQ(GURL(), enumerator->Next());
+ EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
+ EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));
+
+ std::set<GURL> origins_expected;
+ origins_expected.insert(origin());
+
+ for (size_t i = 0; i < arraysize(kOriginEnumerationTestRecords); ++i) {
+ SCOPED_TRACE(testing::Message() <<
+ "Validating kOriginEnumerationTestRecords " << i);
+ const OriginEnumerationTestRecord& record =
+ kOriginEnumerationTestRecords[i];
+ GURL origin_url(record.origin_url);
+ origins_expected.insert(origin_url);
+ if (record.has_temporary) {
+ std::unique_ptr<SandboxFileSystemTestHelper> file_system(
+ NewFileSystem(origin_url, kFileSystemTypeTemporary));
+ std::unique_ptr<FileSystemOperationContext> context(
+ NewContext(file_system.get()));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(),
+ file_system->CreateURLFromUTF8("file"),
+ &created));
+ EXPECT_TRUE(created);
+ }
+ if (record.has_persistent) {
+ std::unique_ptr<SandboxFileSystemTestHelper> file_system(
+ NewFileSystem(origin_url, kFileSystemTypePersistent));
+ std::unique_ptr<FileSystemOperationContext> context(
+ NewContext(file_system.get()));
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(),
+ file_system->CreateURLFromUTF8("file"),
+ &created));
+ EXPECT_TRUE(created);
+ }
+ }
+ enumerator.reset(ofu()->CreateOriginEnumerator());
+ EXPECT_TRUE(enumerator.get());
+ std::set<GURL> origins_found;
+ GURL origin_url;
+ while (!(origin_url = enumerator->Next()).is_empty()) {
+ origins_found.insert(origin_url);
+ SCOPED_TRACE(testing::Message() << "Handling " << origin_url.spec());
+ bool found = false;
+ for (size_t i = 0; !found && i < arraysize(kOriginEnumerationTestRecords);
+ ++i) {
+ const OriginEnumerationTestRecord& record =
+ kOriginEnumerationTestRecords[i];
+ if (origin_url != record.origin_url)
+ continue;
+ found = true;
+ EXPECT_EQ(record.has_temporary,
+ HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
+ EXPECT_EQ(record.has_persistent,
+ HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));
+ }
+ // Deal with the default filesystem created by the test helper.
+ if (!found && origin_url == origin()) {
+ ASSERT_TRUE(type() == kFileSystemTypeTemporary);
+ EXPECT_TRUE(HasFileSystemType(enumerator.get(),
+ kFileSystemTypeTemporary));
+ EXPECT_FALSE(HasFileSystemType(enumerator.get(),
+ kFileSystemTypePersistent));
+ found = true;
+ }
+ EXPECT_TRUE(found);
+ }
+
+ std::set<GURL> diff;
+ std::set_symmetric_difference(origins_expected.begin(),
+ origins_expected.end(), origins_found.begin(), origins_found.end(),
+ inserter(diff, diff.begin()));
+ EXPECT_TRUE(diff.empty());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ int64_t expected_quota = 0;
+
+ for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kRegularTestCase " << i);
+ const FileSystemTestCaseRecord& test_case =
+ kRegularFileSystemTestCases[i];
+ base::FilePath file_path(test_case.path);
+ expected_quota += ObfuscatedFileUtil::ComputeFilePathCost(file_path);
+ if (test_case.is_directory) {
+ bool exclusive = true;
+ bool recursive = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), CreateURL(file_path),
+ exclusive, recursive));
+ } else {
+ bool created = false;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), CreateURL(file_path),
+ &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), CreateURL(file_path),
+ test_case.data_file_size));
+ expected_quota += test_case.data_file_size;
+ }
+ }
+
+ // Usually raw size in usage cache and the usage returned by QuotaUtil
+ // should be same.
+ EXPECT_EQ(expected_quota, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+
+ RevokeUsageCache();
+ EXPECT_EQ(-1, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+
+ // This should reconstruct the cache.
+ GetUsageFromQuotaManager();
+ EXPECT_EQ(expected_quota, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+ EXPECT_EQ(expected_quota, usage());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestInconsistency) {
+ const FileSystemURL kPath1 = CreateURLFromUTF8("hoge");
+ const FileSystemURL kPath2 = CreateURLFromUTF8("fuga");
+
+ std::unique_ptr<FileSystemOperationContext> context;
+ base::File::Info file_info;
+ base::FilePath data_path;
+ bool created = false;
+
+ // Create a non-empty file.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(context.get(), kPath1, 10));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(
+ context.get(), kPath1, &file_info, &data_path));
+ EXPECT_EQ(10, file_info.size);
+
+ // Destroy database to make inconsistency between database and filesystem.
+ ofu()->DestroyDirectoryDatabase(origin(), type_string());
+
+ // Try to get file info of broken file.
+ EXPECT_FALSE(PathExists(kPath1));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(
+ context.get(), kPath1, &file_info, &data_path));
+ EXPECT_EQ(0, file_info.size);
+
+ // Make another broken file to |kPath2|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath2, &created));
+ EXPECT_TRUE(created);
+
+ // Destroy again.
+ ofu()->DestroyDirectoryDatabase(origin(), type_string());
+
+ // Repair broken |kPath1|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->Touch(context.get(), kPath1, base::Time::Now(),
+ base::Time::Now()));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+
+ // Copy from sound |kPath1| to broken |kPath2|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), kPath1, kPath2,
+ FileSystemOperation::OPTION_NONE,
+ true /* copy */));
+
+ ofu()->DestroyDirectoryDatabase(origin(), type_string());
+ context.reset(NewContext(NULL));
+ base::File file =
+ ofu()->CreateOrOpen(context.get(), kPath1,
+ base::File::FLAG_READ | base::File::FLAG_CREATE);
+ EXPECT_TRUE(file.IsValid());
+ EXPECT_TRUE(file.created());
+
+ EXPECT_TRUE(file.GetInfo(&file_info));
+ EXPECT_EQ(0, file_info.size);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) {
+ const FileSystemURL kPath[] = {
+ CreateURLFromUTF8("foo"),
+ CreateURLFromUTF8("bar"),
+ CreateURLFromUTF8("baz")
+ };
+ const FileSystemURL empty_path = CreateURL(base::FilePath());
+ std::unique_ptr<FileSystemOperationContext> context;
+
+ for (size_t i = 0; i < arraysize(kPath); ++i) {
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath[i], &created));
+ EXPECT_TRUE(created);
+ }
+
+ std::vector<storage::DirectoryEntry> entries;
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), empty_path, &entries));
+ EXPECT_EQ(3u, entries.size());
+
+ base::FilePath local_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), kPath[0], &local_path));
+ EXPECT_TRUE(base::DeleteFile(local_path, false));
+
+ entries.clear();
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), empty_path, &entries));
+ EXPECT_EQ(arraysize(kPath) - 1, entries.size());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
+
+ // Create working directory.
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir_url, false, false));
+
+ // EnsureFileExists, create case.
+ FileSystemURL url(FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_file"));
+ bool created = false;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // non create case.
+ created = true;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_FALSE(created);
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_dir");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, false, false));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CreateOrOpen, create case.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateOrOpen_file");
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ base::File file =
+ ofu()->CreateOrOpen(context.get(), url,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+
+ EXPECT_TRUE(file.IsValid());
+ EXPECT_TRUE(file.created());
+ file.Close();
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // open case.
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ file = ofu()->CreateOrOpen(context.get(), url,
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ EXPECT_TRUE(file.IsValid());
+ EXPECT_FALSE(file.created());
+ file.Close();
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ file = ofu()->CreateOrOpen(context.get(), url,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ EXPECT_FALSE(file.IsValid());
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, file.error_details());
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CreateDirectory, create case.
+ // Creating CreateDirectory_dir and CreateDirectory_dir/subdir.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
+ FileSystemURL subdir_url(FileSystemURLAppendUTF8(url, "subdir"));
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), subdir_url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // create subdir case.
+ // Creating CreateDirectory_dir/subdir2.
+ subdir_url = FileSystemURLAppendUTF8(url, "subdir2");
+ ClearTimestamp(dir_url);
+ ClearTimestamp(url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), subdir_url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+ EXPECT_NE(base::Time(), GetModifiedTime(url));
+
+ // fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CopyInForeignFile, create case.
+ url = FileSystemURLAppendUTF8(dir_url, "CopyInForeignFile_file");
+ FileSystemURL src_path = FileSystemURLAppendUTF8(
+ dir_url, "CopyInForeignFile_src_file");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_path, &created));
+ EXPECT_TRUE(created);
+ base::FilePath src_local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), src_path, &src_local_path));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CopyInForeignFile(context.get(),
+ src_local_path,
+ url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) {
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
+
+ // Create working directory.
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir_url, false, false));
+
+ // DeleteFile, delete case.
+ FileSystemURL url = FileSystemURLAppendUTF8(
+ dir_url, "DeleteFile_file");
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case.
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // DeleteDirectory, fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "DeleteDirectory_dir");
+ FileSystemURL file_path(FileSystemURLAppendUTF8(url, "pakeratta"));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, true, true));
+ created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), file_path, &created));
+ EXPECT_TRUE(created);
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
+ ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // delete case.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(context.get(), file_path));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) {
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("copy overwrite"), true, true);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("copy non-overwrite"), true, false);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("move overwrite"), false, true);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("move non-overwrite"), false, false);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) {
+ FileSystemURL dir = CreateURLFromUTF8("foo");
+ FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar");
+ FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz");
+
+ std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir, false, false));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url1, &created));
+ EXPECT_TRUE(created);
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(context.get(), url2, false, false));
+
+ base::FilePath file_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url1, &file_path));
+ EXPECT_FALSE(file_path.empty());
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->Touch(context.get(), url1,
+ base::Time::Now() + base::TimeDelta::FromHours(1),
+ base::Time()));
+
+ context.reset(NewContext(NULL));
+ std::unique_ptr<storage::FileSystemFileUtil::AbstractFileEnumerator>
+ file_enum(ofu()->CreateFileEnumerator(context.get(), dir, false));
+
+ int count = 0;
+ base::FilePath file_path_each;
+ while (!(file_path_each = file_enum->Next()).empty()) {
+ context.reset(NewContext(NULL));
+ base::File::Info file_info;
+ base::FilePath file_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ ofu()->GetFileInfo(context.get(),
+ FileSystemURL::CreateForTest(
+ dir.origin(),
+ dir.mount_type(),
+ file_path_each),
+ &file_info, &file_path));
+ EXPECT_EQ(file_info.is_directory, file_enum->IsDirectory());
+ EXPECT_EQ(file_info.last_modified, file_enum->LastModifiedTime());
+ EXPECT_EQ(file_info.size, file_enum->Size());
+ ++count;
+ }
+ EXPECT_EQ(2, count);
+}
+
+// crbug.com/176470
+#if defined(OS_WIN) || defined(OS_ANDROID)
+#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile
+#else
+#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile
+#endif
+TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) {
+ FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
+ FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
+ FileSystemURL to_file1(CreateURLFromUTF8("tofile1"));
+ FileSystemURL to_file2(CreateURLFromUTF8("tofile2"));
+ bool created;
+
+ int64_t expected_total_file_size = 0;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(obstacle_file))->context(),
+ obstacle_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t obstacle_file_size = 1;
+ expected_total_file_size += obstacle_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(obstacle_file_size)->context(),
+ obstacle_file, obstacle_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t to_file1_size = from_file_size;
+ expected_total_file_size += to_file1_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ PathCost(to_file1) + to_file1_size)->context(),
+ from_file, to_file1,
+ FileSystemOperation::OPTION_NONE,
+ true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(
+ DisallowUsageIncrease(
+ PathCost(to_file2) + from_file_size)->context(),
+ from_file, to_file2, FileSystemOperation::OPTION_NONE,
+ true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ obstacle_file_size - old_obstacle_file_size)->context(),
+ from_file, obstacle_file,
+ FileSystemOperation::OPTION_NONE,
+ true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t old_from_file_size = from_file_size;
+ from_file_size = old_from_file_size - 1;
+ expected_total_file_size += from_file_size - old_from_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(
+ from_file_size - old_from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ // quota exceeded
+ {
+ old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
+ std::unique_ptr<UsageVerifyHelper> helper =
+ AllowUsageIncrease(obstacle_file_size - old_obstacle_file_size);
+ helper->context()->set_allowed_bytes_growth(
+ helper->context()->allowed_bytes_growth() - 1);
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ helper->context(),
+ from_file, obstacle_file,
+ FileSystemOperation::OPTION_NONE,
+ true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+ }
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) {
+ FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
+ FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
+ FileSystemURL to_file(CreateURLFromUTF8("tofile"));
+ bool created;
+
+ int64_t expected_total_file_size = 0;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ from_file_size = 0;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(-PathCost(from_file) +
+ PathCost(to_file))->context(),
+ from_file, to_file,
+ FileSystemOperation::OPTION_NONE,
+ false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(obstacle_file))->context(),
+ obstacle_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t obstacle_file_size = 1;
+ expected_total_file_size += obstacle_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1)->context(),
+ obstacle_file, obstacle_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64_t old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ from_file_size = 0;
+ expected_total_file_size -= old_obstacle_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ -old_obstacle_file_size - PathCost(from_file))->context(),
+ from_file, obstacle_file,
+ FileSystemOperation::OPTION_NONE,
+ false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ from_file_size = 10;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ // quota exceeded even after operation
+ old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ from_file_size = 0;
+ expected_total_file_size -= old_obstacle_file_size;
+ std::unique_ptr<FileSystemOperationContext> context =
+ LimitedContext(-old_obstacle_file_size - PathCost(from_file) - 1);
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CopyOrMoveFile(
+ context.get(), from_file, obstacle_file,
+ FileSystemOperation::OPTION_NONE,
+ false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+ context.reset();
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove) {
+ FileSystemURL dir(CreateURLFromUTF8("dir"));
+ FileSystemURL file(CreateURLFromUTF8("file"));
+ FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1"));
+ FileSystemURL dfile2(CreateURLFromUTF8("dir/dfile2"));
+ bool created;
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(file))->context(),
+ file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->CreateDirectory(
+ AllowUsageIncrease(PathCost(dir))->context(),
+ dir, false, false));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(dfile1))->context(),
+ dfile1, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(dfile2))->context(),
+ dfile2, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(340)->context(),
+ file, 340));
+ ASSERT_EQ(340, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1020)->context(),
+ dfile1, 1020));
+ ASSERT_EQ(1360, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(120)->context(),
+ dfile2, 120));
+ ASSERT_EQ(1480, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->DeleteFile(
+ AllowUsageIncrease(-PathCost(file) - 340)->context(),
+ file));
+ ASSERT_EQ(1140, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::Remove(
+ file_system_context(), dir, true /* recursive */));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen) {
+ FileSystemURL url(CreateURLFromUTF8("file"));
+
+ bool created;
+ // Creating a file.
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(url))->context(),
+ url, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ // Opening it, which shouldn't change the usage.
+ base::File file =
+ ofu()->CreateOrOpen(AllowUsageIncrease(0)->context(), url,
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ file.Close();
+
+ const int length = 33;
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(length)->context(), url, length));
+ ASSERT_EQ(length, ComputeTotalFileSize());
+
+ // Opening it with CREATE_ALWAYS flag, which should truncate the file size.
+ file = ofu()->CreateOrOpen(
+ AllowUsageIncrease(-length)->context(), url,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ file.Close();
+
+ // Extending the file again.
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(length)->context(), url, length));
+ ASSERT_EQ(length, ComputeTotalFileSize());
+
+ // Opening it with TRUNCATED flag, which should truncate the file size.
+ file = ofu()->CreateOrOpen(
+ AllowUsageIncrease(-length)->context(), url,
+ base::File::FLAG_OPEN_TRUNCATED | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ file.Close();
+}
+
+TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) {
+ MaybeDropDatabasesAliveCaseTestBody();
+}
+
+TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) {
+ MaybeDropDatabasesAlreadyDeletedCaseTestBody();
+}
+
+TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) {
+ DestroyDirectoryDatabase_IsolatedTestBody();
+}
+
+TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) {
+ GetDirectoryDatabase_IsolatedTestBody();
+}
+
+TEST_F(ObfuscatedFileUtilTest, MigrationBackFromIsolated) {
+ MigrationBackFromIsolatedTestBody();
+}
+
+TEST_F(ObfuscatedFileUtilTest, OpenPathInNonDirectory) {
+ FileSystemURL url(CreateURLFromUTF8("file"));
+ FileSystemURL path_in_file(CreateURLFromUTF8("file/file"));
+ bool created;
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(UnlimitedContext().get(), url, &created));
+ ASSERT_TRUE(created);
+
+ int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
+ base::File file =
+ ofu()->CreateOrOpen(UnlimitedContext().get(), path_in_file, file_flags);
+ ASSERT_FALSE(file.IsValid());
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY, file.error_details());
+
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
+ ofu()->CreateDirectory(UnlimitedContext().get(),
+ path_in_file,
+ false /* exclusive */,
+ false /* recursive */));
+}
+
+TEST_F(ObfuscatedFileUtilTest, CreateDirectory_NotADirectoryInRecursive) {
+ FileSystemURL file(CreateURLFromUTF8("file"));
+ FileSystemURL path_in_file(CreateURLFromUTF8("file/child"));
+ FileSystemURL path_in_file_in_file(
+ CreateURLFromUTF8("file/child/grandchild"));
+ bool created;
+
+ ASSERT_EQ(base::File::FILE_OK,
+ ofu()->EnsureFileExists(UnlimitedContext().get(), file, &created));
+ ASSERT_TRUE(created);
+
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
+ ofu()->CreateDirectory(UnlimitedContext().get(),
+ path_in_file,
+ false /* exclusive */,
+ true /* recursive */));
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
+ ofu()->CreateDirectory(UnlimitedContext().get(),
+ path_in_file_in_file,
+ false /* exclusive */,
+ true /* recursive */));
+}
+
+TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType) {
+ const GURL origin1("http://www.example.com:12");
+ const GURL origin2("http://www.example.com:1234");
+
+ // Create origin directories.
+ std::unique_ptr<SandboxFileSystemTestHelper> fs1(
+ NewFileSystem(origin1, kFileSystemTypeTemporary));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs2(
+ NewFileSystem(origin1, kFileSystemTypePersistent));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs3(
+ NewFileSystem(origin2, kFileSystemTypeTemporary));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs4(
+ NewFileSystem(origin2, kFileSystemTypePersistent));
+
+ // Make sure directories for origin1 exist.
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Make sure directories for origin2 exist.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Delete a directory for origin1's persistent filesystem.
+ ofu()->DeleteDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypePersistent));
+
+ // The directory for origin1's temporary filesystem should not be removed.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // The directory for origin1's persistent filesystem should be removed.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);
+
+ // The directories for origin2 should not be removed.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+}
+
+TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType_DeleteAll) {
+ const GURL origin1("http://www.example.com:12");
+ const GURL origin2("http://www.example.com:1234");
+
+ // Create origin directories.
+ std::unique_ptr<SandboxFileSystemTestHelper> fs1(
+ NewFileSystem(origin1, kFileSystemTypeTemporary));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs2(
+ NewFileSystem(origin1, kFileSystemTypePersistent));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs3(
+ NewFileSystem(origin2, kFileSystemTypeTemporary));
+ std::unique_ptr<SandboxFileSystemTestHelper> fs4(
+ NewFileSystem(origin2, kFileSystemTypePersistent));
+
+ // Make sure directories for origin1 exist.
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Make sure directories for origin2 exist.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Delete all directories for origin1.
+ ofu()->DeleteDirectoryForOriginAndType(origin1, std::string());
+
+ // The directories for origin1 should be removed.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);
+
+ // The directories for origin2 should not be removed.
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ error = base::File::FILE_ERROR_FAILED;
+ ofu()->GetDirectoryForOriginAndType(
+ origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
+ ASSERT_EQ(base::File::FILE_OK, error);
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/plugin_private_file_system_backend.cc b/chromium/storage/browser/fileapi/plugin_private_file_system_backend.cc
index efb51f74cb3..cebc136569d 100644
--- a/chromium/storage/browser/fileapi/plugin_private_file_system_backend.cc
+++ b/chromium/storage/browser/fileapi/plugin_private_file_system_backend.cc
@@ -37,7 +37,7 @@ class PluginPrivateFileSystemBackend::FileSystemIDToPluginMap {
~FileSystemIDToPluginMap() {}
std::string GetPluginIDForURL(const FileSystemURL& url) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
Map::iterator found = map_.find(url.filesystem_id());
if (url.type() != kFileSystemTypePluginPrivate || found == map_.end()) {
NOTREACHED() << "Unsupported url is given: " << url.DebugString();
@@ -48,14 +48,14 @@ class PluginPrivateFileSystemBackend::FileSystemIDToPluginMap {
void RegisterFileSystem(const std::string& filesystem_id,
const std::string& plugin_id) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(!filesystem_id.empty());
DCHECK(!base::ContainsKey(map_, filesystem_id)) << filesystem_id;
map_[filesystem_id] = plugin_id;
}
void RemoveFileSystem(const std::string& filesystem_id) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
map_.erase(filesystem_id);
}
@@ -113,7 +113,7 @@ PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend(
}
PluginPrivateFileSystemBackend::~PluginPrivateFileSystemBackend() {
- if (!file_task_runner_->RunsTasksOnCurrentThread()) {
+ if (!file_task_runner_->RunsTasksInCurrentSequence()) {
AsyncFileUtil* file_util = file_util_.release();
if (!file_task_runner_->DeleteSoon(FROM_HERE, file_util))
delete file_util;
@@ -268,7 +268,7 @@ int64_t PluginPrivateFileSystemBackend::GetOriginUsageOnFileTaskRunner(
FileSystemContext* context,
const GURL& origin_url,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
if (!CanHandleType(type))
return 0;
@@ -285,7 +285,7 @@ void PluginPrivateFileSystemBackend::GetOriginDetailsOnFileTaskRunner(
const GURL& origin_url,
int64_t* total_size,
base::Time* last_modified_time) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
*total_size = 0;
*last_modified_time = base::Time::UnixEpoch();
diff --git a/chromium/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc b/chromium/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc
new file mode 100644
index 00000000000..fda7911d6d6
--- /dev/null
+++ b/chromium/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc
@@ -0,0 +1,288 @@
+// Copyright 2013 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 <memory>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/plugin_private_file_system_backend.h"
+#include "storage/browser/test/async_file_test_helper.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/browser/test/test_file_system_options.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::AsyncFileTestHelper;
+using storage::FileSystemContext;
+using storage::FileSystemURL;
+using storage::IsolatedContext;
+
+namespace content {
+
+namespace {
+
+const GURL kOrigin1("http://www.example.com");
+const GURL kOrigin2("https://www.example.com");
+const std::string kPlugin1("plugin1");
+const std::string kPlugin2("plugin2");
+const storage::FileSystemType kType = storage::kFileSystemTypePluginPrivate;
+const std::string kRootName = "pluginprivate";
+
+void DidOpenFileSystem(base::File::Error* error_out,
+ base::File::Error error) {
+ *error_out = error;
+}
+
+std::string RegisterFileSystem() {
+ return IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
+ kType, kRootName, base::FilePath());
+}
+
+} // namespace
+
+class PluginPrivateFileSystemBackendTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ context_ = CreateFileSystemContextForTesting(NULL /* quota_manager_proxy */,
+ data_dir_.GetPath());
+ }
+
+ FileSystemURL CreateURL(const GURL& root_url, const std::string& relative) {
+ FileSystemURL root = context_->CrackURL(root_url);
+ return context_->CreateCrackedFileSystemURL(
+ root.origin(),
+ root.mount_type(),
+ root.virtual_path().AppendASCII(relative));
+ }
+
+ storage::PluginPrivateFileSystemBackend* backend() const {
+ return context_->plugin_private_backend();
+ }
+
+ const base::FilePath& base_path() const { return backend()->base_path(); }
+
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_refptr<FileSystemContext> context_;
+};
+
+// TODO(kinuko,nhiroki): There are a lot of duplicate code in these tests. Write
+// helper functions to simplify the tests.
+
+TEST_F(PluginPrivateFileSystemBackendTest, OpenFileSystemBasic) {
+ const std::string filesystem_id1 = RegisterFileSystem();
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id1,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Run this again with FAIL_IF_NONEXISTENT to see if it succeeds.
+ const std::string filesystem_id2 = RegisterFileSystem();
+ error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id2,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const GURL root_url(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id1, kRootName));
+ FileSystemURL file = CreateURL(root_url, "foo");
+ base::FilePath platform_path;
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file));
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::GetPlatformPath(context_.get(), file,
+ &platform_path));
+ EXPECT_TRUE(base_path().AppendASCII("000").AppendASCII(kPlugin1).IsParent(
+ platform_path));
+}
+
+TEST_F(PluginPrivateFileSystemBackendTest, PluginIsolation) {
+ // Open filesystem for kPlugin1 and kPlugin2.
+ const std::string filesystem_id1 = RegisterFileSystem();
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id1,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const std::string filesystem_id2 = RegisterFileSystem();
+ error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id2,
+ kPlugin2,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Create 'foo' in kPlugin1.
+ const GURL root_url1(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id1, kRootName));
+ FileSystemURL file1 = CreateURL(root_url1, "foo");
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file1));
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
+
+ // See the same path is not available in kPlugin2.
+ const GURL root_url2(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id2, kRootName));
+ FileSystemURL file2 = CreateURL(root_url2, "foo");
+ EXPECT_FALSE(AsyncFileTestHelper::FileExists(
+ context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
+}
+
+TEST_F(PluginPrivateFileSystemBackendTest, OriginIsolation) {
+ // Open filesystem for kOrigin1 and kOrigin2.
+ const std::string filesystem_id1 = RegisterFileSystem();
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id1,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const std::string filesystem_id2 = RegisterFileSystem();
+ error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin2,
+ kType,
+ filesystem_id2,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Create 'foo' in kOrigin1.
+ const GURL root_url1(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id1, kRootName));
+ FileSystemURL file1 = CreateURL(root_url1, "foo");
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file1));
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
+
+ // See the same path is not available in kOrigin2.
+ const GURL root_url2(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin2, filesystem_id2, kRootName));
+ FileSystemURL file2 = CreateURL(root_url2, "foo");
+ EXPECT_FALSE(AsyncFileTestHelper::FileExists(
+ context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
+}
+
+TEST_F(PluginPrivateFileSystemBackendTest, DeleteOriginDirectory) {
+ // Open filesystem for kOrigin1 and kOrigin2.
+ const std::string filesystem_id1 = RegisterFileSystem();
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id1,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const std::string filesystem_id2 = RegisterFileSystem();
+ error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin2,
+ kType,
+ filesystem_id2,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Create 'foo' in kOrigin1.
+ const GURL root_url1(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id1, kRootName));
+ FileSystemURL file1 = CreateURL(root_url1, "foo");
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file1));
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
+
+ // Create 'foo' in kOrigin2.
+ const GURL root_url2(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin2, filesystem_id2, kRootName));
+ FileSystemURL file2 = CreateURL(root_url2, "foo");
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file2));
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
+
+ // Delete data for kOrigin1.
+ error = backend()->DeleteOriginDataOnFileTaskRunner(
+ context_.get(), NULL, kOrigin1, kType);
+ EXPECT_EQ(base::File::FILE_OK, error);
+
+ // Confirm 'foo' in kOrigin1 is deleted.
+ EXPECT_FALSE(AsyncFileTestHelper::FileExists(
+ context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
+
+ // Confirm 'foo' in kOrigin2 is NOT deleted.
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
+
+ // Re-open filesystem for kOrigin1.
+ const std::string filesystem_id3 = RegisterFileSystem();
+ error = base::File::FILE_ERROR_FAILED;
+ backend()->OpenPrivateFileSystem(
+ kOrigin1,
+ kType,
+ filesystem_id3,
+ kPlugin1,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ // Re-create 'foo' in kOrigin1.
+ const GURL root_url3(storage::GetIsolatedFileSystemRootURIString(
+ kOrigin1, filesystem_id3, kRootName));
+ FileSystemURL file3 = CreateURL(root_url3, "foo");
+ EXPECT_EQ(base::File::FILE_OK,
+ AsyncFileTestHelper::CreateFile(context_.get(), file3));
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file3, AsyncFileTestHelper::kDontCheckSize));
+
+ // Confirm 'foo' in kOrigin1 is re-created.
+ EXPECT_TRUE(AsyncFileTestHelper::FileExists(
+ context_.get(), file3, AsyncFileTestHelper::kDontCheckSize));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/quota/quota_backend_impl.cc b/chromium/storage/browser/fileapi/quota/quota_backend_impl.cc
index 4ec600fe9dc..0cce9eceed7 100644
--- a/chromium/storage/browser/fileapi/quota/quota_backend_impl.cc
+++ b/chromium/storage/browser/fileapi/quota/quota_backend_impl.cc
@@ -39,7 +39,7 @@ void QuotaBackendImpl::ReserveQuota(const GURL& origin,
FileSystemType type,
int64_t delta,
const ReserveQuotaCallback& callback) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
if (!delta) {
callback.Run(base::File::FILE_OK, 0);
@@ -59,7 +59,7 @@ void QuotaBackendImpl::ReserveQuota(const GURL& origin,
void QuotaBackendImpl::ReleaseReservedQuota(const GURL& origin,
FileSystemType type,
int64_t size) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
DCHECK_LE(0, size);
if (!size)
@@ -70,7 +70,7 @@ void QuotaBackendImpl::ReleaseReservedQuota(const GURL& origin,
void QuotaBackendImpl::CommitQuotaUsage(const GURL& origin,
FileSystemType type,
int64_t delta) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
if (!delta)
return;
@@ -84,7 +84,7 @@ void QuotaBackendImpl::CommitQuotaUsage(const GURL& origin,
void QuotaBackendImpl::IncrementDirtyCount(const GURL& origin,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
base::FilePath path;
if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK)
@@ -95,7 +95,7 @@ void QuotaBackendImpl::IncrementDirtyCount(const GURL& origin,
void QuotaBackendImpl::DecrementDirtyCount(const GURL& origin,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
base::FilePath path;
if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK)
@@ -110,7 +110,7 @@ void QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota(
storage::QuotaStatusCode status,
int64_t usage,
int64_t quota) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(info.origin.is_valid());
DCHECK_LE(0, usage);
DCHECK_LE(0, quota);
@@ -140,7 +140,7 @@ void QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota(
}
void QuotaBackendImpl::ReserveQuotaInternal(const QuotaReservationInfo& info) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(info.origin.is_valid());
DCHECK(quota_manager_proxy_.get());
quota_manager_proxy_->NotifyStorageModified(
@@ -154,7 +154,7 @@ base::File::Error QuotaBackendImpl::GetUsageCachePath(
const GURL& origin,
FileSystemType type,
base::FilePath* usage_file_path) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origin.is_valid());
DCHECK(usage_file_path);
base::File::Error error = base::File::FILE_OK;
diff --git a/chromium/storage/browser/fileapi/recursive_operation_delegate_unittest.cc b/chromium/storage/browser/fileapi/recursive_operation_delegate_unittest.cc
new file mode 100644
index 00000000000..34026337e6f
--- /dev/null
+++ b/chromium/storage/browser/fileapi/recursive_operation_delegate_unittest.cc
@@ -0,0 +1,389 @@
+// Copyright 2013 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 "storage/browser/fileapi/recursive_operation_delegate.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/test/sandbox_file_system_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using storage::FileSystemContext;
+using storage::FileSystemOperationContext;
+using storage::FileSystemURL;
+
+namespace content {
+namespace {
+
+class LoggingRecursiveOperation : public storage::RecursiveOperationDelegate {
+ public:
+ struct LogEntry {
+ enum Type {
+ PROCESS_FILE,
+ PROCESS_DIRECTORY,
+ POST_PROCESS_DIRECTORY
+ };
+ Type type;
+ FileSystemURL url;
+ };
+
+ LoggingRecursiveOperation(FileSystemContext* file_system_context,
+ const FileSystemURL& root,
+ const StatusCallback& callback)
+ : storage::RecursiveOperationDelegate(file_system_context),
+ root_(root),
+ callback_(callback),
+ weak_factory_(this) {}
+ ~LoggingRecursiveOperation() override {}
+
+ const std::vector<LogEntry>& log_entries() const { return log_entries_; }
+
+ // RecursiveOperationDelegate overrides.
+ void Run() override { NOTREACHED(); }
+
+ void RunRecursively() override {
+ StartRecursiveOperation(
+ root_, storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT, callback_);
+ }
+
+ void RunRecursivelyWithIgnoringError() {
+ StartRecursiveOperation(
+ root_, storage::FileSystemOperation::ERROR_BEHAVIOR_SKIP, callback_);
+ }
+
+ void ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) override {
+ RecordLogEntry(LogEntry::PROCESS_FILE, url);
+
+ if (error_url_.is_valid() && error_url_ == url) {
+ callback.Run(base::File::FILE_ERROR_FAILED);
+ return;
+ }
+
+ operation_runner()->GetMetadata(
+ url, storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY,
+ base::Bind(&LoggingRecursiveOperation::DidGetMetadata,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) override {
+ RecordLogEntry(LogEntry::PROCESS_DIRECTORY, url);
+ callback.Run(base::File::FILE_OK);
+ }
+
+ void PostProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) override {
+ RecordLogEntry(LogEntry::POST_PROCESS_DIRECTORY, url);
+ callback.Run(base::File::FILE_OK);
+ }
+
+ void SetEntryToFail(const FileSystemURL& url) { error_url_ = url; }
+
+ private:
+ void RecordLogEntry(LogEntry::Type type, const FileSystemURL& url) {
+ LogEntry entry;
+ entry.type = type;
+ entry.url = url;
+ log_entries_.push_back(entry);
+ }
+
+ void DidGetMetadata(const StatusCallback& callback,
+ base::File::Error result,
+ const base::File::Info& file_info) {
+ if (result != base::File::FILE_OK) {
+ callback.Run(result);
+ return;
+ }
+
+ callback.Run(file_info.is_directory ?
+ base::File::FILE_ERROR_NOT_A_FILE :
+ base::File::FILE_OK);
+ }
+
+ FileSystemURL root_;
+ StatusCallback callback_;
+ std::vector<LogEntry> log_entries_;
+ FileSystemURL error_url_;
+
+ base::WeakPtrFactory<LoggingRecursiveOperation> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(LoggingRecursiveOperation);
+};
+
+void ReportStatus(base::File::Error* out_error,
+ base::File::Error error) {
+ DCHECK(out_error);
+ *out_error = error;
+}
+
+// To test the Cancel() during operation, calls Cancel() of |operation|
+// after |counter| times message posting.
+void CallCancelLater(storage::RecursiveOperationDelegate* operation,
+ int counter) {
+ if (counter > 0) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&CallCancelLater, base::Unretained(operation), counter - 1));
+ return;
+ }
+
+ operation->Cancel();
+}
+
+} // namespace
+
+class RecursiveOperationDelegateTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ sandbox_file_system_.SetUp(base_.GetPath().AppendASCII("filesystem"));
+ }
+
+ void TearDown() override { sandbox_file_system_.TearDown(); }
+
+ std::unique_ptr<FileSystemOperationContext> NewContext() {
+ FileSystemOperationContext* context =
+ sandbox_file_system_.NewOperationContext();
+ // Grant enough quota for all test cases.
+ context->set_allowed_bytes_growth(1000000);
+ return base::WrapUnique(context);
+ }
+
+ storage::FileSystemFileUtil* file_util() {
+ return sandbox_file_system_.file_util();
+ }
+
+ FileSystemURL URLForPath(const std::string& path) const {
+ return sandbox_file_system_.CreateURLFromUTF8(path);
+ }
+
+ FileSystemURL CreateFile(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ bool created = false;
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->EnsureFileExists(NewContext().get(),
+ url, &created));
+ EXPECT_TRUE(created);
+ return url;
+ }
+
+ FileSystemURL CreateDirectory(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ EXPECT_EQ(base::File::FILE_OK,
+ file_util()->CreateDirectory(NewContext().get(), url,
+ false /* exclusive */, true));
+ return url;
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ // Common temp base for nondestructive uses.
+ base::ScopedTempDir base_;
+ SandboxFileSystemTestHelper sandbox_file_system_;
+};
+
+TEST_F(RecursiveOperationDelegateTest, RootIsFile) {
+ FileSystemURL src_file(CreateFile("src"));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ std::unique_ptr<FileSystemOperationContext> context = NewContext();
+ std::unique_ptr<LoggingRecursiveOperation> operation(
+ new LoggingRecursiveOperation(context->file_system_context(), src_file,
+ base::Bind(&ReportStatus, &error)));
+ operation->RunRecursively();
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
+ operation->log_entries();
+ ASSERT_EQ(1U, log_entries.size());
+ const LoggingRecursiveOperation::LogEntry& entry = log_entries[0];
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, entry.type);
+ EXPECT_EQ(src_file, entry.url);
+}
+
+TEST_F(RecursiveOperationDelegateTest, RootIsDirectory) {
+ FileSystemURL src_root(CreateDirectory("src"));
+ FileSystemURL src_dir1(CreateDirectory("src/dir1"));
+ FileSystemURL src_file1(CreateFile("src/file1"));
+ FileSystemURL src_file2(CreateFile("src/dir1/file2"));
+ FileSystemURL src_file3(CreateFile("src/dir1/file3"));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ std::unique_ptr<FileSystemOperationContext> context = NewContext();
+ std::unique_ptr<LoggingRecursiveOperation> operation(
+ new LoggingRecursiveOperation(context->file_system_context(), src_root,
+ base::Bind(&ReportStatus, &error)));
+ operation->RunRecursively();
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_OK, error);
+
+ const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
+ operation->log_entries();
+ ASSERT_EQ(8U, log_entries.size());
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[0].type);
+ EXPECT_EQ(src_root, log_entries[0].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
+ log_entries[1].type);
+ EXPECT_EQ(src_root, log_entries[1].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[2].type);
+ EXPECT_EQ(src_file1, log_entries[2].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
+ log_entries[3].type);
+ EXPECT_EQ(src_dir1, log_entries[3].url);
+
+ // The order of src/dir1/file2 and src/dir1/file3 depends on the file system
+ // implementation (can be swapped).
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[4].type);
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[5].type);
+ EXPECT_TRUE((src_file2 == log_entries[4].url &&
+ src_file3 == log_entries[5].url) ||
+ (src_file3 == log_entries[4].url &&
+ src_file2 == log_entries[5].url));
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
+ log_entries[6].type);
+ EXPECT_EQ(src_dir1, log_entries[6].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
+ log_entries[7].type);
+ EXPECT_EQ(src_root, log_entries[7].url);
+}
+
+TEST_F(RecursiveOperationDelegateTest, Cancel) {
+ FileSystemURL src_root(CreateDirectory("src"));
+ FileSystemURL src_dir1(CreateDirectory("src/dir1"));
+ FileSystemURL src_file1(CreateFile("src/file1"));
+ FileSystemURL src_file2(CreateFile("src/dir1/file2"));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ std::unique_ptr<FileSystemOperationContext> context = NewContext();
+ std::unique_ptr<LoggingRecursiveOperation> operation(
+ new LoggingRecursiveOperation(context->file_system_context(), src_root,
+ base::Bind(&ReportStatus, &error)));
+ operation->RunRecursively();
+
+ // Invoke Cancel(), after 5 times message posting.
+ CallCancelLater(operation.get(), 5);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(base::File::FILE_ERROR_ABORT, error);
+}
+
+TEST_F(RecursiveOperationDelegateTest, AbortWithError) {
+ FileSystemURL src_root(CreateDirectory("src"));
+ FileSystemURL src_dir1(CreateDirectory("src/dir1"));
+ FileSystemURL src_file1(CreateFile("src/file1"));
+ FileSystemURL src_file2(CreateFile("src/dir1/file2"));
+ FileSystemURL src_file3(CreateFile("src/dir1/file3"));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ std::unique_ptr<FileSystemOperationContext> context = NewContext();
+ std::unique_ptr<LoggingRecursiveOperation> operation(
+ new LoggingRecursiveOperation(context->file_system_context(), src_root,
+ base::Bind(&ReportStatus, &error)));
+ operation->SetEntryToFail(src_file1);
+ operation->RunRecursively();
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(base::File::FILE_ERROR_FAILED, error);
+
+ // Confirm that operation has been aborted in the middle.
+ const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
+ operation->log_entries();
+ ASSERT_EQ(3U, log_entries.size());
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[0].type);
+ EXPECT_EQ(src_root, log_entries[0].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
+ log_entries[1].type);
+ EXPECT_EQ(src_root, log_entries[1].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[2].type);
+ EXPECT_EQ(src_file1, log_entries[2].url);
+}
+
+TEST_F(RecursiveOperationDelegateTest, ContinueWithError) {
+ FileSystemURL src_root(CreateDirectory("src"));
+ FileSystemURL src_dir1(CreateDirectory("src/dir1"));
+ FileSystemURL src_file1(CreateFile("src/file1"));
+ FileSystemURL src_file2(CreateFile("src/dir1/file2"));
+ FileSystemURL src_file3(CreateFile("src/dir1/file3"));
+
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ std::unique_ptr<FileSystemOperationContext> context = NewContext();
+ std::unique_ptr<LoggingRecursiveOperation> operation(
+ new LoggingRecursiveOperation(context->file_system_context(), src_root,
+ base::Bind(&ReportStatus, &error)));
+ operation->SetEntryToFail(src_file1);
+ operation->RunRecursivelyWithIgnoringError();
+ base::RunLoop().RunUntilIdle();
+
+ // Error code should be base::File::FILE_ERROR_FAILED.
+ ASSERT_EQ(base::File::FILE_ERROR_FAILED, error);
+
+ // Confirm that operation continues after the error.
+ const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
+ operation->log_entries();
+ ASSERT_EQ(8U, log_entries.size());
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[0].type);
+ EXPECT_EQ(src_root, log_entries[0].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
+ log_entries[1].type);
+ EXPECT_EQ(src_root, log_entries[1].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[2].type);
+ EXPECT_EQ(src_file1, log_entries[2].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
+ log_entries[3].type);
+ EXPECT_EQ(src_dir1, log_entries[3].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[4].type);
+ EXPECT_EQ(src_file3, log_entries[4].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
+ log_entries[5].type);
+ EXPECT_EQ(src_file2, log_entries[5].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
+ log_entries[6].type);
+ EXPECT_EQ(src_dir1, log_entries[6].url);
+
+ EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
+ log_entries[7].type);
+ EXPECT_EQ(src_root, log_entries[7].url);
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/sandbox_directory_database_unittest.cc b/chromium/storage/browser/fileapi/sandbox_directory_database_unittest.cc
new file mode 100644
index 00000000000..30df6655df4
--- /dev/null
+++ b/chromium/storage/browser/fileapi/sandbox_directory_database_unittest.cc
@@ -0,0 +1,677 @@
+// 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 "storage/browser/fileapi/sandbox_directory_database.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "storage/browser/test/sandbox_database_test_helper.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+using storage::FilePathToString;
+using storage::SandboxDirectoryDatabase;
+
+namespace content {
+
+namespace {
+const base::FilePath::CharType kDirectoryDatabaseName[] = FPL("Paths");
+}
+
+class SandboxDirectoryDatabaseTest : public testing::Test {
+ public:
+ typedef SandboxDirectoryDatabase::FileId FileId;
+ typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ SandboxDirectoryDatabaseTest() {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ InitDatabase();
+ }
+
+ SandboxDirectoryDatabase* db() {
+ return db_.get();
+ }
+
+ void InitDatabase() {
+ // Call CloseDatabase() to avoid having multiple database instances for
+ // single directory at once.
+ CloseDatabase();
+ db_.reset(new SandboxDirectoryDatabase(path(), NULL));
+ }
+
+ void CloseDatabase() {
+ db_.reset();
+ }
+
+ base::File::Error AddFileInfo(
+ FileId parent_id, const base::FilePath::StringType& name) {
+ FileId file_id;
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ return db_->AddFileInfo(info, &file_id);
+ }
+
+ void CreateDirectory(FileId parent_id,
+ const base::FilePath::StringType& name,
+ FileId* file_id_out) {
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ ASSERT_EQ(base::File::FILE_OK, db_->AddFileInfo(info, file_id_out));
+ }
+
+ void CreateFile(FileId parent_id,
+ const base::FilePath::StringType& name,
+ const base::FilePath::StringType& data_path,
+ FileId* file_id_out) {
+ FileId file_id;
+
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ info.data_path = base::FilePath(data_path).NormalizePathSeparators();
+ ASSERT_EQ(base::File::FILE_OK, db_->AddFileInfo(info, &file_id));
+
+ base::FilePath local_path = path().Append(data_path);
+ if (!base::DirectoryExists(local_path.DirName()))
+ ASSERT_TRUE(base::CreateDirectory(local_path.DirName()));
+
+ base::File file(local_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+
+ if (file_id_out)
+ *file_id_out = file_id;
+ }
+
+ void ClearDatabaseAndDirectory() {
+ db_.reset();
+ ASSERT_TRUE(base::DeleteFile(path(), true /* recursive */));
+ ASSERT_TRUE(base::CreateDirectory(path()));
+ db_.reset(new SandboxDirectoryDatabase(path(), NULL));
+ }
+
+ bool RepairDatabase() {
+ return db()->RepairDatabase(
+ FilePathToString(path().Append(kDirectoryDatabaseName)));
+ }
+
+ const base::FilePath& path() { return base_.GetPath(); }
+
+ // Makes link from |parent_id| to |child_id| with |name|.
+ void MakeHierarchyLink(FileId parent_id,
+ FileId child_id,
+ const base::FilePath::StringType& name) {
+ ASSERT_TRUE(db()->db_->Put(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(parent_id) + ":" +
+ FilePathToString(base::FilePath(name)),
+ base::Int64ToString(child_id)).ok());
+ }
+
+ // Deletes link from parent of |file_id| to |file_id|.
+ void DeleteHierarchyLink(FileId file_id) {
+ FileInfo file_info;
+ ASSERT_TRUE(db()->GetFileInfo(file_id, &file_info));
+ ASSERT_TRUE(db()->db_->Delete(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(file_info.parent_id) + ":" +
+ FilePathToString(base::FilePath(file_info.name))).ok());
+ }
+
+ protected:
+ // Common temp base for nondestructive uses.
+ base::ScopedTempDir base_;
+ std::unique_ptr<SandboxDirectoryDatabase> db_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabaseTest);
+};
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMissingFileGetInfo) {
+ FileId file_id = 888;
+ FileInfo info;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetRootFileInfoBeforeCreate) {
+ FileId file_id = 0;
+ FileInfo info;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info));
+ EXPECT_EQ(0, info.parent_id);
+ EXPECT_TRUE(info.name.empty());
+ EXPECT_TRUE(info.data_path.empty());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMissingParentAddFileInfo) {
+ FileId parent_id = 7;
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
+ AddFileInfo(parent_id, FILE_PATH_LITERAL("foo")));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestAddNameClash) {
+ FileInfo info;
+ FileId file_id;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("dir 0");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id));
+
+ // Check for name clash in the root directory.
+ base::FilePath::StringType name = info.name;
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, AddFileInfo(0, name));
+ name = FILE_PATH_LITERAL("dir 1");
+ EXPECT_EQ(base::File::FILE_OK, AddFileInfo(0, name));
+
+ name = FILE_PATH_LITERAL("subdir 0");
+ EXPECT_EQ(base::File::FILE_OK, AddFileInfo(file_id, name));
+
+ // Check for name clash in a subdirectory.
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, AddFileInfo(file_id, name));
+ name = FILE_PATH_LITERAL("subdir 1");
+ EXPECT_EQ(base::File::FILE_OK, AddFileInfo(file_id, name));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRenameNoMoveNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ EXPECT_EQ(base::File::FILE_OK, AddFileInfo(0, name1));
+ info.name = name1;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id0, info));
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id0, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMoveSameNameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMoveRenameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+ // Also test a successful move+rename.
+ info.parent_id = file_id0;
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRemoveWithChildren) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("foo");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ EXPECT_FALSE(db()->RemoveFileInfo(file_id0));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id1));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id0));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetChildWithName) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ EXPECT_NE(file_id0, file_id1);
+
+ FileId check_file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, name1, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(0, name0, &check_file_id));
+ EXPECT_EQ(file_id0, check_file_id);
+ EXPECT_FALSE(db()->GetChildWithName(file_id0, name0, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(file_id0, name1, &check_file_id));
+ EXPECT_EQ(file_id1, check_file_id);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetFileWithPath) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ FileId file_id2;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("dog");
+
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ EXPECT_NE(file_id0, file_id1);
+ info.parent_id = file_id1;
+ info.name = name2;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id2));
+ EXPECT_NE(file_id0, file_id2);
+ EXPECT_NE(file_id1, file_id2);
+
+ FileId check_file_id;
+ base::FilePath path = base::FilePath(name0);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id0, check_file_id);
+
+ path = path.Append(name1);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id1, check_file_id);
+
+ path = path.Append(name2);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id2, check_file_id);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestListChildren) {
+ // No children in the root.
+ std::vector<FileId> children;
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in the root.
+ FileId file_id0;
+ FileInfo info;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("foo");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(children.size(), 1UL);
+ EXPECT_EQ(children[0], file_id0);
+
+ // Two children in the root.
+ FileId file_id1;
+ info.name = FILE_PATH_LITERAL("bar");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id0) {
+ EXPECT_EQ(children[1], file_id1);
+ } else {
+ EXPECT_EQ(children[1], file_id0);
+ EXPECT_EQ(children[0], file_id1);
+ }
+
+ // No children in a subdirectory.
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in a subdirectory.
+ info.parent_id = file_id0;
+ info.name = FILE_PATH_LITERAL("foo");
+ FileId file_id2;
+ FileId file_id3;
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id2));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(1UL, children.size());
+ EXPECT_EQ(children[0], file_id2);
+
+ // Two children in a subdirectory.
+ info.name = FILE_PATH_LITERAL("bar");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id3));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id2) {
+ EXPECT_EQ(children[1], file_id3);
+ } else {
+ EXPECT_EQ(children[1], file_id2);
+ EXPECT_EQ(children[0], file_id3);
+ }
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestUpdateModificationTime) {
+ FileInfo info0;
+ FileId file_id;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("name");
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("fake path"));
+ info0.modification_time = base::Time::Now();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(
+ floor(info0.modification_time.ToDoubleT()),
+ info1.modification_time.ToDoubleT());
+
+ EXPECT_TRUE(db()->UpdateModificationTime(file_id, base::Time::UnixEpoch()));
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_NE(info0.modification_time, info1.modification_time);
+ EXPECT_EQ(
+ info1.modification_time.ToDoubleT(),
+ floor(base::Time::UnixEpoch().ToDoubleT()));
+
+ EXPECT_FALSE(db()->UpdateModificationTime(999, base::Time::UnixEpoch()));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestSimpleFileOperations) {
+ FileId file_id = 888;
+ FileInfo info0;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info0));
+ info0.parent_id = 0;
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
+ info0.name = FILE_PATH_LITERAL("file name");
+ info0.modification_time = base::Time::Now();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(
+ floor(info0.modification_time.ToDoubleT()),
+ info1.modification_time.ToDoubleT());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSrcDirectory) {
+ FileId directory_id;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("directory");
+ info0.modification_time = base::Time::Now();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &directory_id));
+
+ FileId file_id;
+ FileInfo info1;
+ info1.parent_id = 0;
+ info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info1.name = FILE_PATH_LITERAL("file");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &file_id));
+
+ EXPECT_FALSE(db()->OverwritingMoveFile(directory_id, file_id));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileDestDirectory) {
+ FileId file_id;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("file");
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info0.modification_time = base::Time::Now();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));
+
+ FileId directory_id;
+ FileInfo info1;
+ info1.parent_id = 0;
+ info1.name = FILE_PATH_LITERAL("directory");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &directory_id));
+
+ EXPECT_FALSE(db()->OverwritingMoveFile(file_id, directory_id));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSuccess) {
+ FileId file_id0;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
+ info0.name = FILE_PATH_LITERAL("file name 0");
+ info0.modification_time = base::Time::Now();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id0));
+
+ FileInfo dir_info;
+ FileId dir_id;
+ dir_info.parent_id = 0;
+ dir_info.name = FILE_PATH_LITERAL("directory name");
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(dir_info, &dir_id));
+
+ FileId file_id1;
+ FileInfo info1;
+ info1.parent_id = dir_id;
+ info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info1.name = FILE_PATH_LITERAL("file name 1");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &file_id1));
+
+ EXPECT_TRUE(db()->OverwritingMoveFile(file_id0, file_id1));
+
+ FileInfo check_info;
+ FileId check_id;
+
+ EXPECT_FALSE(db()->GetFileWithPath(base::FilePath(info0.name), &check_id));
+ EXPECT_TRUE(db()->GetFileWithPath(
+ base::FilePath(dir_info.name).Append(info1.name), &check_id));
+ EXPECT_TRUE(db()->GetFileInfo(check_id, &check_info));
+
+ EXPECT_EQ(info0.data_path, check_info.data_path);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetNextInteger) {
+ int64_t next = -1;
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(0, next);
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(1, next);
+ InitDatabase();
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(2, next);
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(3, next);
+ InitDatabase();
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(4, next);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Empty) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ int64_t next = -1;
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(0, next);
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Consistent) {
+ FileId dir_id;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateDirectory(0, FPL("bar"), &dir_id);
+ CreateFile(dir_id, FPL("baz"), FPL("fuga"), NULL);
+ CreateFile(dir_id, FPL("fizz"), FPL("buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest,
+ TestConsistencyCheck_BackingMultiEntry) {
+ const base::FilePath::CharType kBackingFileName[] = FPL("the celeb");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
+ CreateFile(0, FPL("bar"), kBackingFileName, NULL);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_FileLost) {
+ const base::FilePath::CharType kBackingFileName[] = FPL("hoge");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_OrphanFile) {
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ base::File file(path().Append(FPL("Orphan File")),
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(file.IsValid());
+ ASSERT_TRUE(file.created());
+ file.Close();
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_RootLoop) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(0, 0, base::FilePath::StringType());
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_DirectoryLoop) {
+ FileId dir1_id;
+ FileId dir2_id;
+ base::FilePath::StringType dir1_name = FPL("foo");
+ CreateDirectory(0, dir1_name, &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(dir2_id, dir1_id, dir1_name);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_NameMismatch) {
+ FileId dir_id;
+ FileId file_id;
+ CreateDirectory(0, FPL("foo"), &dir_id);
+ CreateFile(dir_id, FPL("bar"), FPL("hoge/fuga/piyo"), &file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(file_id);
+ MakeHierarchyLink(dir_id, file_id, FPL("baz"));
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_WreckedEntries) {
+ FileId dir1_id;
+ FileId dir2_id;
+ CreateDirectory(0, FPL("foo"), &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+ CreateFile(dir2_id, FPL("baz"), FPL("fizz/buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(dir2_id); // Delete link from |dir1_id| to |dir2_id|.
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Success) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ FileId file_id_prev;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_EQ(file_id_prev, file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Failure) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), NULL);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ CorruptDatabase(kDatabaseDirectory, leveldb::kLogFile,
+ -1, 1);
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_MissingManifest) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ FileId file_id_prev;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+
+ DeleteDatabaseFile(kDatabaseDirectory, leveldb::kDescriptorFile);
+
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_EQ(file_id_prev, file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc b/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
index 9063fb0210e..641368d1037 100644
--- a/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
+++ b/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
@@ -203,10 +203,10 @@ SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate(
is_filesystem_opened_(false),
weak_factory_(this) {
// Prepopulate database only if it can run asynchronously (i.e. the current
- // thread is not file_task_runner). Usually this is the case but may not
+ // sequence is not file_task_runner). Usually this is the case but may not
// in test code.
if (!file_system_options.is_incognito() &&
- !file_task_runner_->RunsTasksOnCurrentThread()) {
+ !file_task_runner_->RunsTasksInCurrentSequence()) {
std::vector<std::string> types_to_prepopulate(
&kPrepopulateTypes[0],
&kPrepopulateTypes[arraysize(kPrepopulateTypes)]);
@@ -221,7 +221,7 @@ SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate(
SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() {
io_thread_checker_.DetachFromThread();
- if (!file_task_runner_->RunsTasksOnCurrentThread()) {
+ if (!file_task_runner_->RunsTasksInCurrentSequence()) {
DeleteSoon(file_task_runner_.get(), quota_reservation_manager_.release());
DeleteSoon(file_task_runner_.get(), sandbox_file_util_.release());
DeleteSoon(file_task_runner_.get(), quota_observer_.release());
@@ -331,7 +331,7 @@ SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileTaskRunner(
storage::QuotaManagerProxy* proxy,
const GURL& origin_url,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
int64_t usage =
GetOriginUsageOnFileTaskRunner(file_system_context, origin_url, type);
usage_cache()->CloseCacheFiles();
@@ -351,7 +351,7 @@ SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileTaskRunner(
void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileTaskRunner(
FileSystemType type, std::set<GURL>* origins) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origins);
std::unique_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator());
GURL origin;
@@ -374,7 +374,7 @@ void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileTaskRunner(
void SandboxFileSystemBackendDelegate::GetOriginsForHostOnFileTaskRunner(
FileSystemType type, const std::string& host,
std::set<GURL>* origins) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(origins);
std::unique_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator());
GURL origin;
@@ -389,7 +389,7 @@ int64_t SandboxFileSystemBackendDelegate::GetOriginUsageOnFileTaskRunner(
FileSystemContext* file_system_context,
const GURL& origin_url,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
// Don't use usage cache and return recalculated usage for sticky invalidated
// origins.
@@ -430,7 +430,7 @@ scoped_refptr<QuotaReservation>
SandboxFileSystemBackendDelegate::CreateQuotaReservationOnFileTaskRunner(
const GURL& origin,
FileSystemType type) {
- DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
DCHECK(quota_reservation_manager_);
return quota_reservation_manager_->CreateReservation(origin, type);
}
@@ -658,7 +658,7 @@ void SandboxFileSystemBackendDelegate::CopyFileSystem(
const GURL& origin_url,
FileSystemType type,
SandboxFileSystemBackendDelegate* destination) {
- DCHECK(file_task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(file_task_runner()->RunsTasksInCurrentSequence());
base::FilePath base_path =
GetBaseDirectoryForOriginAndType(origin_url, type, false /* create */);
diff --git a/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc b/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc
new file mode 100644
index 00000000000..8098a09ff34
--- /dev/null
+++ b/chromium/storage/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc
@@ -0,0 +1,88 @@
+// Copyright 2013 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 "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/test/test_file_system_options.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::FileSystemURL;
+
+namespace content {
+
+namespace {
+
+FileSystemURL CreateFileSystemURL(const char* path) {
+ const GURL kOrigin("http://foo/");
+ return storage::FileSystemURL::CreateForTest(
+ kOrigin,
+ storage::kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe(path));
+}
+
+} // namespace
+
+class SandboxFileSystemBackendDelegateTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ delegate_.reset(new storage::SandboxFileSystemBackendDelegate(
+ NULL /* quota_manager_proxy */,
+ base::ThreadTaskRunnerHandle::Get().get(), data_dir_.GetPath(),
+ NULL /* special_storage_policy */, CreateAllowFileAccessOptions()));
+ }
+
+ bool IsAccessValid(const FileSystemURL& url) const {
+ return delegate_->IsAccessValid(url);
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ std::unique_ptr<storage::SandboxFileSystemBackendDelegate> delegate_;
+};
+
+TEST_F(SandboxFileSystemBackendDelegateTest, IsAccessValid) {
+ // Normal case.
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL("a")));
+
+ // Access to a path with parent references ('..') should be disallowed.
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL("a/../b")));
+
+ // Access from non-allowed scheme should be disallowed.
+ EXPECT_FALSE(IsAccessValid(
+ FileSystemURL::CreateForTest(GURL("unknown://bar"),
+ storage::kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe("foo"))));
+
+ // Access with restricted name should be disallowed.
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL(".")));
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL("..")));
+
+ // This is also disallowed due to Windows XP parent path handling.
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL("...")));
+
+ // These are identified as unsafe cases due to weird path handling
+ // on Windows.
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL(" ..")));
+ EXPECT_FALSE(IsAccessValid(CreateFileSystemURL(".. ")));
+
+ // Similar but safe cases.
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL(" .")));
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL(". ")));
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL("b.")));
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL(".b")));
+
+ // A path that looks like a drive letter.
+ EXPECT_TRUE(IsAccessValid(CreateFileSystemURL("c:")));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/sandbox_file_system_backend_unittest.cc b/chromium/storage/browser/fileapi/sandbox_file_system_backend_unittest.cc
new file mode 100644
index 00000000000..125a619a2ca
--- /dev/null
+++ b/chromium/storage/browser/fileapi/sandbox_file_system_backend_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright 2013 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 "storage/browser/fileapi/sandbox_file_system_backend.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <set>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/test/test_file_system_options.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::FileSystemURL;
+using storage::SandboxFileSystemBackend;
+using storage::SandboxFileSystemBackendDelegate;
+
+// PS stands for path separator.
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+#define PS "\\"
+#else
+#define PS "/"
+#endif
+
+namespace content {
+
+namespace {
+
+const struct RootPathTest {
+ storage::FileSystemType type;
+ const char* origin_url;
+ const char* expected_path;
+} kRootPathTestCases[] = {
+ {storage::kFileSystemTypeTemporary, "http://foo:1/", "000" PS "t"},
+ {storage::kFileSystemTypePersistent, "http://foo:1/", "000" PS "p"},
+ {storage::kFileSystemTypeTemporary, "http://bar.com/", "001" PS "t"},
+ {storage::kFileSystemTypePersistent, "http://bar.com/", "001" PS "p"},
+ {storage::kFileSystemTypeTemporary, "https://foo:2/", "002" PS "t"},
+ {storage::kFileSystemTypePersistent, "https://foo:2/", "002" PS "p"},
+ {storage::kFileSystemTypeTemporary, "https://bar.com/", "003" PS "t"},
+ {storage::kFileSystemTypePersistent, "https://bar.com/", "003" PS "p"},
+};
+
+const struct RootPathFileURITest {
+ storage::FileSystemType type;
+ const char* origin_url;
+ const char* expected_path;
+} kRootPathFileURITestCases[] = {
+ {storage::kFileSystemTypeTemporary, "file:///", "000" PS "t"},
+ {storage::kFileSystemTypePersistent, "file:///", "000" PS "p"}};
+
+void DidOpenFileSystem(base::File::Error* error_out,
+ const GURL& origin_url,
+ const std::string& name,
+ base::File::Error error) {
+ *error_out = error;
+}
+
+} // namespace
+
+class SandboxFileSystemBackendTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ SetUpNewDelegate(CreateAllowFileAccessOptions());
+ }
+
+ void SetUpNewDelegate(const storage::FileSystemOptions& options) {
+ delegate_.reset(new SandboxFileSystemBackendDelegate(
+ NULL /* quota_manager_proxy */,
+ base::ThreadTaskRunnerHandle::Get().get(), data_dir_.GetPath(),
+ NULL /* special_storage_policy */, options));
+ }
+
+ void SetUpNewBackend(const storage::FileSystemOptions& options) {
+ SetUpNewDelegate(options);
+ backend_.reset(new SandboxFileSystemBackend(delegate_.get()));
+ }
+
+ storage::SandboxFileSystemBackendDelegate::OriginEnumerator*
+ CreateOriginEnumerator() const {
+ return backend_->CreateOriginEnumerator();
+ }
+
+ void CreateOriginTypeDirectory(const GURL& origin,
+ storage::FileSystemType type) {
+ base::FilePath target = delegate_->
+ GetBaseDirectoryForOriginAndType(origin, type, true);
+ ASSERT_TRUE(!target.empty());
+ ASSERT_TRUE(base::DirectoryExists(target));
+ }
+
+ bool GetRootPath(const GURL& origin_url,
+ storage::FileSystemType type,
+ storage::OpenFileSystemMode mode,
+ base::FilePath* root_path) {
+ base::File::Error error = base::File::FILE_OK;
+ backend_->ResolveURL(
+ FileSystemURL::CreateForTest(origin_url, type, base::FilePath()),
+ mode,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::RunLoop().RunUntilIdle();
+ if (error != base::File::FILE_OK)
+ return false;
+ base::FilePath returned_root_path =
+ delegate_->GetBaseDirectoryForOriginAndType(
+ origin_url, type, false /* create */);
+ if (root_path)
+ *root_path = returned_root_path;
+ return !returned_root_path.empty();
+ }
+
+ base::FilePath file_system_path() const {
+ return data_dir_.GetPath().Append(
+ SandboxFileSystemBackendDelegate::kFileSystemDirectory);
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ std::unique_ptr<storage::SandboxFileSystemBackendDelegate> delegate_;
+ std::unique_ptr<storage::SandboxFileSystemBackend> backend_;
+};
+
+TEST_F(SandboxFileSystemBackendTest, Empty) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ std::unique_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator>
+ enumerator(CreateOriginEnumerator());
+ ASSERT_TRUE(enumerator->Next().is_empty());
+}
+
+TEST_F(SandboxFileSystemBackendTest, EnumerateOrigins) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ const char* temporary_origins[] = {
+ "http://www.bar.com/",
+ "http://www.foo.com/",
+ "http://www.foo.com:1/",
+ "http://www.example.com:8080/",
+ "http://www.google.com:80/",
+ };
+ const char* persistent_origins[] = {
+ "http://www.bar.com/",
+ "http://www.foo.com:8080/",
+ "http://www.foo.com:80/",
+ };
+ size_t temporary_size = arraysize(temporary_origins);
+ size_t persistent_size = arraysize(persistent_origins);
+ std::set<GURL> temporary_set, persistent_set;
+ for (size_t i = 0; i < temporary_size; ++i) {
+ CreateOriginTypeDirectory(GURL(temporary_origins[i]),
+ storage::kFileSystemTypeTemporary);
+ temporary_set.insert(GURL(temporary_origins[i]));
+ }
+ for (size_t i = 0; i < persistent_size; ++i) {
+ CreateOriginTypeDirectory(GURL(persistent_origins[i]),
+ storage::kFileSystemTypePersistent);
+ persistent_set.insert(GURL(persistent_origins[i]));
+ }
+
+ std::unique_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator>
+ enumerator(CreateOriginEnumerator());
+ size_t temporary_actual_size = 0;
+ size_t persistent_actual_size = 0;
+ GURL current;
+ while (!(current = enumerator->Next()).is_empty()) {
+ SCOPED_TRACE(testing::Message() << "EnumerateOrigin " << current.spec());
+ if (enumerator->HasFileSystemType(storage::kFileSystemTypeTemporary)) {
+ ASSERT_TRUE(temporary_set.find(current) != temporary_set.end());
+ ++temporary_actual_size;
+ }
+ if (enumerator->HasFileSystemType(storage::kFileSystemTypePersistent)) {
+ ASSERT_TRUE(persistent_set.find(current) != persistent_set.end());
+ ++persistent_actual_size;
+ }
+ }
+
+ EXPECT_EQ(temporary_size, temporary_actual_size);
+ EXPECT_EQ(persistent_size, persistent_actual_size);
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) {
+ std::vector<base::FilePath> returned_root_path(arraysize(kRootPathTestCases));
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+
+ // Create a new root directory.
+ for (size_t i = 0; i < arraysize(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path));
+
+ base::FilePath expected = file_system_path().AppendASCII(
+ kRootPathTestCases[i].expected_path);
+ EXPECT_EQ(expected.value(), root_path.value());
+ EXPECT_TRUE(base::DirectoryExists(root_path));
+ ASSERT_TRUE(returned_root_path.size() > i);
+ returned_root_path[i] = root_path;
+ }
+
+ // Get the root directory with create=false and see if we get the
+ // same directory.
+ for (size_t i = 0; i < arraysize(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (get) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ &root_path));
+ ASSERT_TRUE(returned_root_path.size() > i);
+ EXPECT_EQ(returned_root_path[i].value(), root_path.value());
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest,
+ GetRootPathCreateAndExamineWithNewBackend) {
+ std::vector<base::FilePath> returned_root_path(arraysize(kRootPathTestCases));
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+
+ GURL origin_url("http://foo.com:1/");
+
+ base::FilePath root_path1;
+ EXPECT_TRUE(GetRootPath(origin_url,
+ storage::kFileSystemTypeTemporary,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path1));
+
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+ base::FilePath root_path2;
+ EXPECT_TRUE(GetRootPath(origin_url,
+ storage::kFileSystemTypeTemporary,
+ storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ &root_path2));
+
+ EXPECT_EQ(root_path1.value(), root_path2.value());
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathGetWithoutCreate) {
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+
+ // Try to get a root directory without creating.
+ for (size_t i = 0; i < arraysize(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (create=false) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+ EXPECT_FALSE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathInIncognito) {
+ SetUpNewBackend(CreateIncognitoFileSystemOptions());
+
+ // Try to get a root directory.
+ for (size_t i = 0; i < arraysize(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (incognito) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+ EXPECT_FALSE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURI) {
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+ for (size_t i = 0; i < arraysize(kRootPathFileURITestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #"
+ << i << " " << kRootPathFileURITestCases[i].expected_path);
+ EXPECT_FALSE(GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url),
+ kRootPathFileURITestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURIWithAllowFlag) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ for (size_t i = 0; i < arraysize(kRootPathFileURITestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #"
+ << i << " " << kRootPathFileURITestCases[i].expected_path);
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url),
+ kRootPathFileURITestCases[i].type,
+ storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path));
+ base::FilePath expected = file_system_path().AppendASCII(
+ kRootPathFileURITestCases[i].expected_path);
+ EXPECT_EQ(expected.value(), root_path.value());
+ EXPECT_TRUE(base::DirectoryExists(root_path));
+ }
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/sandbox_origin_database_unittest.cc b/chromium/storage/browser/fileapi/sandbox_origin_database_unittest.cc
new file mode 100644
index 00000000000..e2675a7696d
--- /dev/null
+++ b/chromium/storage/browser/fileapi/sandbox_origin_database_unittest.cc
@@ -0,0 +1,309 @@
+// 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 <stddef.h>
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+#include "storage/browser/test/sandbox_database_test_helper.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/db/filename.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+using storage::SandboxOriginDatabase;
+
+namespace content {
+
+namespace {
+const base::FilePath::CharType kFileSystemDirName[] =
+ FILE_PATH_LITERAL("File System");
+const base::FilePath::CharType kOriginDatabaseName[] =
+ FILE_PATH_LITERAL("Origins");
+} // namespace
+
+TEST(SandboxOriginDatabaseTest, BasicTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir, NULL);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+ // Double-check to make sure that had no side effects.
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path0;
+ base::FilePath path1;
+
+ // Empty strings aren't valid origins.
+ EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path0));
+
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path0.empty());
+ EXPECT_FALSE(path1.empty());
+ EXPECT_EQ(path0, path1);
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+}
+
+TEST(SandboxOriginDatabaseTest, TwoPathTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir, NULL);
+ std::string origin0("origin0");
+ std::string origin1("origin1");
+
+ EXPECT_FALSE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+
+ base::FilePath path0;
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+ EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1));
+ EXPECT_TRUE(database.HasOriginPath(origin1));
+ EXPECT_FALSE(path0.empty());
+ EXPECT_FALSE(path1.empty());
+ EXPECT_NE(path0, path1);
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+}
+
+TEST(SandboxOriginDatabaseTest, DropDatabaseTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir, NULL);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path0;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_FALSE(path0.empty());
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+
+ database.DropDatabase();
+
+ base::FilePath path1;
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path1.empty());
+ EXPECT_EQ(path0, path1);
+}
+
+TEST(SandboxOriginDatabaseTest, DeleteOriginTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir, NULL);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.RemovePathForOrigin(origin));
+
+ base::FilePath path0;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_FALSE(path0.empty());
+
+ EXPECT_TRUE(database.RemovePathForOrigin(origin));
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path1.empty());
+ EXPECT_NE(path0, path1);
+}
+
+TEST(SandboxOriginDatabaseTest, ListOriginsTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ std::vector<SandboxOriginDatabase::OriginRecord> origins;
+
+ SandboxOriginDatabase database(kFSDir, NULL);
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_TRUE(origins.empty());
+ origins.clear();
+
+ std::string origin0("origin0");
+ std::string origin1("origin1");
+
+ EXPECT_FALSE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+
+ base::FilePath path0;
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0));
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_EQ(origins.size(), 1UL);
+ EXPECT_EQ(origins[0].origin, origin0);
+ EXPECT_EQ(origins[0].path, path0);
+ origins.clear();
+ EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1));
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_EQ(origins.size(), 2UL);
+ if (origins[0].origin == origin0) {
+ EXPECT_EQ(origins[0].path, path0);
+ EXPECT_EQ(origins[1].origin, origin1);
+ EXPECT_EQ(origins[1].path, path1);
+ } else {
+ EXPECT_EQ(origins[0].origin, origin1);
+ EXPECT_EQ(origins[0].path, path1);
+ EXPECT_EQ(origins[1].origin, origin0);
+ EXPECT_EQ(origins[1].path, path0);
+ }
+}
+
+TEST(SandboxOriginDatabaseTest, DatabaseRecoveryTest) {
+ // Checks if SandboxOriginDatabase properly handles database corruption.
+ // In this test, we'll register some origins to the origin database, then
+ // corrupt database and its log file.
+ // After repairing, the origin database should be consistent even when some
+ // entries lost.
+
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ const std::string kOrigins[] = {
+ "foo.example.com",
+ "bar.example.com",
+ "baz.example.com",
+ "hoge.example.com",
+ "fuga.example.com",
+ };
+
+ std::unique_ptr<SandboxOriginDatabase> database(
+ new SandboxOriginDatabase(kFSDir, NULL));
+ for (size_t i = 0; i < arraysize(kOrigins); ++i) {
+ base::FilePath path;
+ EXPECT_FALSE(database->HasOriginPath(kOrigins[i]));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path));
+
+ if (i != 1)
+ EXPECT_TRUE(base::CreateDirectory(kFSDir.Append(path)));
+ }
+ database.reset();
+
+ const base::FilePath kGarbageDir = kFSDir.AppendASCII("foo");
+ const base::FilePath kGarbageFile = kGarbageDir.AppendASCII("bar");
+ EXPECT_TRUE(base::CreateDirectory(kGarbageDir));
+ base::File file(kGarbageFile,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ EXPECT_TRUE(file.IsValid());
+ file.Close();
+
+ // Corrupt database itself and last log entry to drop last 1 database
+ // operation. The database should detect the corruption and should recover
+ // its consistency after recovery.
+ CorruptDatabase(kDBDir, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ CorruptDatabase(kDBDir, leveldb::kLogFile, -1, 1);
+
+ base::FilePath path;
+ database.reset(new SandboxOriginDatabase(kFSDir, NULL));
+ std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db;
+ EXPECT_TRUE(database->ListAllOrigins(&origins_in_db));
+
+ // Expect all but last added origin will be repaired back, and kOrigins[1]
+ // should be dropped due to absence of backing directory.
+ EXPECT_EQ(arraysize(kOrigins) - 2, origins_in_db.size());
+
+ const std::string kOrigin("piyo.example.org");
+ EXPECT_FALSE(database->HasOriginPath(kOrigin));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->HasOriginPath(kOrigin));
+
+ EXPECT_FALSE(base::PathExists(kGarbageFile));
+ EXPECT_FALSE(base::PathExists(kGarbageDir));
+}
+
+TEST(SandboxOriginDatabaseTest, DatabaseRecoveryForMissingDBFileTest) {
+ const leveldb::FileType kLevelDBFileTypes[] = {
+ leveldb::kLogFile,
+ leveldb::kDBLockFile,
+ leveldb::kTableFile,
+ leveldb::kDescriptorFile,
+ leveldb::kCurrentFile,
+ leveldb::kTempFile,
+ leveldb::kInfoLogFile,
+ };
+
+ for (size_t i = 0; i < arraysize(kLevelDBFileTypes); ++i) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.GetPath().Append(kFileSystemDirName);
+ const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir));
+
+ const std::string kOrigin = "foo.example.com";
+ base::FilePath path;
+
+ std::unique_ptr<SandboxOriginDatabase> database(
+ new SandboxOriginDatabase(kFSDir, NULL));
+ EXPECT_FALSE(database->HasOriginPath(kOrigin));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_TRUE(base::CreateDirectory(kFSDir.Append(path)));
+ database.reset();
+
+ DeleteDatabaseFile(kDBDir, kLevelDBFileTypes[i]);
+
+ database.reset(new SandboxOriginDatabase(kFSDir, NULL));
+ std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db;
+ EXPECT_TRUE(database->ListAllOrigins(&origins_in_db));
+
+ const std::string kOrigin2("piyo.example.org");
+ EXPECT_FALSE(database->HasOriginPath(kOrigin2));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin2, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->HasOriginPath(kOrigin2));
+ }
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/fileapi/sandbox_quota_observer.cc b/chromium/storage/browser/fileapi/sandbox_quota_observer.cc
index 0b96b87bd62..e9b5e3f2371 100644
--- a/chromium/storage/browser/fileapi/sandbox_quota_observer.cc
+++ b/chromium/storage/browser/fileapi/sandbox_quota_observer.cc
@@ -30,7 +30,7 @@ SandboxQuotaObserver::SandboxQuotaObserver(
SandboxQuotaObserver::~SandboxQuotaObserver() {}
void SandboxQuotaObserver::OnStartUpdate(const FileSystemURL& url) {
- DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+ DCHECK(update_notify_runner_->RunsTasksInCurrentSequence());
base::FilePath usage_file_path = GetUsageCachePath(url);
if (usage_file_path.empty())
return;
@@ -38,7 +38,7 @@ void SandboxQuotaObserver::OnStartUpdate(const FileSystemURL& url) {
}
void SandboxQuotaObserver::OnUpdate(const FileSystemURL& url, int64_t delta) {
- DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+ DCHECK(update_notify_runner_->RunsTasksInCurrentSequence());
if (quota_manager_proxy_.get()) {
quota_manager_proxy_->NotifyStorageModified(
@@ -65,7 +65,7 @@ void SandboxQuotaObserver::OnUpdate(const FileSystemURL& url, int64_t delta) {
}
void SandboxQuotaObserver::OnEndUpdate(const FileSystemURL& url) {
- DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+ DCHECK(update_notify_runner_->RunsTasksInCurrentSequence());
base::FilePath usage_file_path = GetUsageCachePath(url);
if (usage_file_path.empty())
diff --git a/chromium/storage/browser/fileapi/task_runner_bound_observer_list.h b/chromium/storage/browser/fileapi/task_runner_bound_observer_list.h
index eae9ddf18d4..d035dd0a25f 100644
--- a/chromium/storage/browser/fileapi/task_runner_bound_observer_list.h
+++ b/chromium/storage/browser/fileapi/task_runner_bound_observer_list.h
@@ -69,7 +69,7 @@ class TaskRunnerBoundObserverList {
"bad unbound method params");
for (typename ObserversListMap::const_iterator it = observers_.begin();
it != observers_.end(); ++it) {
- if (!it->second.get() || it->second->RunsTasksOnCurrentThread()) {
+ if (!it->second.get() || it->second->RunsTasksInCurrentSequence()) {
base::DispatchToMethod(it->first, method, params);
continue;
}
diff --git a/chromium/storage/browser/fileapi/timed_task_helper.cc b/chromium/storage/browser/fileapi/timed_task_helper.cc
index 17adbd763a2..0ea3f094791 100644
--- a/chromium/storage/browser/fileapi/timed_task_helper.cc
+++ b/chromium/storage/browser/fileapi/timed_task_helper.cc
@@ -36,7 +36,7 @@ TimedTaskHelper::~TimedTaskHelper() {
}
bool TimedTaskHelper::IsRunning() const {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
return tracker_ != NULL;
}
@@ -51,7 +51,7 @@ void TimedTaskHelper::Start(
}
void TimedTaskHelper::Reset() {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(!user_task_.is_null());
desired_run_time_ = base::TimeTicks::Now() + delay_;
@@ -72,7 +72,7 @@ void TimedTaskHelper::Fired(std::unique_ptr<Tracker> tracker) {
}
void TimedTaskHelper::OnFired(std::unique_ptr<Tracker> tracker) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
base::TimeTicks now = base::TimeTicks::Now();
if (desired_run_time_ > now) {
PostDelayedTask(std::move(tracker), desired_run_time_ - now);
diff --git a/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc b/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc
index e3475dfe932..9b5276afa8b 100644
--- a/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc
+++ b/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc
@@ -6,6 +6,7 @@
#include "base/bind.h"
#include "base/location.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
diff --git a/chromium/storage/browser/fileapi/transient_file_util_unittest.cc b/chromium/storage/browser/fileapi/transient_file_util_unittest.cc
new file mode 100644
index 00000000000..01536dfcd8d
--- /dev/null
+++ b/chromium/storage/browser/fileapi/transient_file_util_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2013 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 <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "storage/browser/blob/scoped_file.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/transient_file_util.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using storage::FileSystemURL;
+
+namespace content {
+
+class TransientFileUtilTest : public testing::Test {
+ public:
+ TransientFileUtilTest() {}
+ ~TransientFileUtilTest() override {}
+
+ void SetUp() override {
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, base::FilePath(FILE_PATH_LITERAL("dummy")));
+ transient_file_util_.reset(new storage::TransientFileUtil);
+
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ }
+
+ void TearDown() override {
+ file_system_context_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void CreateAndRegisterTemporaryFile(
+ FileSystemURL* file_url,
+ base::FilePath* file_path) {
+ EXPECT_TRUE(base::CreateTemporaryFileInDir(data_dir_.GetPath(), file_path));
+ storage::IsolatedContext* isolated_context =
+ storage::IsolatedContext::GetInstance();
+ std::string name = "tmp";
+ std::string fsid = isolated_context->RegisterFileSystemForPath(
+ storage::kFileSystemTypeForTransientFile,
+ std::string(),
+ *file_path,
+ &name);
+ ASSERT_TRUE(!fsid.empty());
+ base::FilePath virtual_path = isolated_context->CreateVirtualRootPath(
+ fsid).AppendASCII(name);
+ *file_url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://foo"), storage::kFileSystemTypeIsolated, virtual_path);
+ }
+
+ std::unique_ptr<storage::FileSystemOperationContext> NewOperationContext() {
+ return base::MakeUnique<storage::FileSystemOperationContext>(
+ file_system_context_.get());
+ }
+
+ storage::FileSystemFileUtil* file_util() {
+ return transient_file_util_.get();
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::ScopedTempDir data_dir_;
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+ std::unique_ptr<storage::TransientFileUtil> transient_file_util_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransientFileUtilTest);
+};
+
+TEST_F(TransientFileUtilTest, TransientFile) {
+ FileSystemURL temp_url;
+ base::FilePath temp_path;
+
+ CreateAndRegisterTemporaryFile(&temp_url, &temp_path);
+
+ base::File::Error error;
+ base::File::Info file_info;
+ base::FilePath path;
+
+ // Make sure the file is there.
+ ASSERT_TRUE(temp_url.is_valid());
+ ASSERT_TRUE(base::PathExists(temp_path));
+ ASSERT_FALSE(base::DirectoryExists(temp_path));
+
+ // Create a snapshot file.
+ {
+ storage::ScopedFile scoped_file = file_util()->CreateSnapshotFile(
+ NewOperationContext().get(), temp_url, &error, &file_info, &path);
+ ASSERT_EQ(base::File::FILE_OK, error);
+ ASSERT_EQ(temp_path, path);
+ ASSERT_FALSE(file_info.is_directory);
+
+ // The file should be still there.
+ ASSERT_TRUE(base::PathExists(temp_path));
+ ASSERT_EQ(base::File::FILE_OK,
+ file_util()->GetFileInfo(NewOperationContext().get(),
+ temp_url, &file_info, &path));
+ ASSERT_EQ(temp_path, path);
+ ASSERT_FALSE(file_info.is_directory);
+ }
+
+ // The file's now scoped out.
+ base::RunLoop().RunUntilIdle();
+
+ // Now the temporary file and the transient filesystem must be gone too.
+ ASSERT_FALSE(base::PathExists(temp_path));
+ ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND,
+ file_util()->GetFileInfo(NewOperationContext().get(),
+ temp_url, &file_info, &path));
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/quota/OWNERS b/chromium/storage/browser/quota/OWNERS
index 66ba5d26390..09c46c2b568 100644
--- a/chromium/storage/browser/quota/OWNERS
+++ b/chromium/storage/browser/quota/OWNERS
@@ -1 +1,4 @@
tzik@chromium.org
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>Quota
diff --git a/chromium/storage/browser/quota/quota_database_unittest.cc b/chromium/storage/browser/quota/quota_database_unittest.cc
new file mode 100644
index 00000000000..625c864d518
--- /dev/null
+++ b/chromium/storage/browser/quota/quota_database_unittest.cc
@@ -0,0 +1,698 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <iterator>
+#include <set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/test/scoped_error_expecter.h"
+#include "sql/test/test_helpers.h"
+#include "storage/browser/quota/quota_database.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::kStorageTypePersistent;
+using storage::kStorageTypeTemporary;
+using storage::QuotaDatabase;
+
+namespace content {
+namespace {
+
+const base::Time kZeroTime;
+
+const char kDBFileName[] = "quota_manager.db";
+
+} // namespace
+
+class QuotaDatabaseTest : public testing::Test {
+ protected:
+ typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry;
+ typedef QuotaDatabase::QuotaTableCallback QuotaTableCallback;
+ typedef QuotaDatabase::OriginInfoTableCallback
+ OriginInfoTableCallback;
+
+ void LazyOpen(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ EXPECT_FALSE(db.LazyOpen(false));
+ ASSERT_TRUE(db.LazyOpen(true));
+ EXPECT_TRUE(db.db_.get());
+ EXPECT_TRUE(kDbFile.empty() || base::PathExists(kDbFile));
+ }
+
+ void Reopen(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(false));
+ EXPECT_TRUE(db.db_.get());
+ EXPECT_TRUE(kDbFile.empty() || base::PathExists(kDbFile));
+ }
+
+ void UpgradeSchemaV2toV5(const base::FilePath& kDbFile) {
+ const QuotaTableEntry entries[] = {
+ QuotaTableEntry("a", kStorageTypeTemporary, 1),
+ QuotaTableEntry("b", kStorageTypeTemporary, 2),
+ QuotaTableEntry("c", kStorageTypePersistent, 3),
+ };
+
+ CreateV2Database(kDbFile, entries, arraysize(entries));
+
+ QuotaDatabase db(kDbFile);
+ EXPECT_TRUE(db.LazyOpen(true));
+ EXPECT_TRUE(db.db_.get());
+
+ typedef EntryVerifier<QuotaTableEntry> Verifier;
+ Verifier verifier(entries, entries + arraysize(entries));
+ EXPECT_TRUE(db.DumpQuotaTable(
+ base::Bind(&Verifier::Run, base::Unretained(&verifier))));
+ EXPECT_TRUE(verifier.table.empty());
+
+ EXPECT_TRUE(db.db_->DoesTableExist("EvictionInfoTable"));
+ EXPECT_TRUE(db.db_->DoesIndexExist("sqlite_autoindex_EvictionInfoTable_1"));
+ }
+
+ void HostQuota(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ const char* kHost = "foo.com";
+ const int kQuota1 = 13579;
+ const int kQuota2 = kQuota1 + 1024;
+
+ int64_t quota = -1;
+ EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypeTemporary, &quota));
+ EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, &quota));
+
+ // Insert quota for temporary.
+ EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota1));
+ EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota1, quota);
+
+ // Update quota for temporary.
+ EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota2));
+ EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota2, quota);
+
+ // Quota for persistent must not be updated.
+ EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, &quota));
+
+ // Delete temporary storage quota.
+ EXPECT_TRUE(db.DeleteHostQuota(kHost, kStorageTypeTemporary));
+ EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypeTemporary, &quota));
+ }
+
+ void GlobalQuota(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ const char* kTempQuotaKey = QuotaDatabase::kTemporaryQuotaOverrideKey;
+ const char* kAvailSpaceKey = QuotaDatabase::kDesiredAvailableSpaceKey;
+
+ int64_t value = 0;
+ const int64_t kValue1 = 456;
+ const int64_t kValue2 = 123000;
+ EXPECT_FALSE(db.GetQuotaConfigValue(kTempQuotaKey, &value));
+ EXPECT_FALSE(db.GetQuotaConfigValue(kAvailSpaceKey, &value));
+
+ EXPECT_TRUE(db.SetQuotaConfigValue(kTempQuotaKey, kValue1));
+ EXPECT_TRUE(db.GetQuotaConfigValue(kTempQuotaKey, &value));
+ EXPECT_EQ(kValue1, value);
+
+ EXPECT_TRUE(db.SetQuotaConfigValue(kTempQuotaKey, kValue2));
+ EXPECT_TRUE(db.GetQuotaConfigValue(kTempQuotaKey, &value));
+ EXPECT_EQ(kValue2, value);
+
+ EXPECT_TRUE(db.SetQuotaConfigValue(kAvailSpaceKey, kValue1));
+ EXPECT_TRUE(db.GetQuotaConfigValue(kAvailSpaceKey, &value));
+ EXPECT_EQ(kValue1, value);
+
+ EXPECT_TRUE(db.SetQuotaConfigValue(kAvailSpaceKey, kValue2));
+ EXPECT_TRUE(db.GetQuotaConfigValue(kAvailSpaceKey, &value));
+ EXPECT_EQ(kValue2, value);
+ }
+
+ void OriginLastAccessTimeLRU(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ std::set<GURL> exceptions;
+ GURL origin;
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_TRUE(origin.is_empty());
+
+ const GURL kOrigin1("http://a/");
+ const GURL kOrigin2("http://b/");
+ const GURL kOrigin3("http://c/");
+ const GURL kOrigin4("http://p/");
+
+ // Adding three temporary storages, and
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(10)));
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(20)));
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(30)));
+
+ // one persistent.
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin4, kStorageTypePersistent, base::Time::FromInternalValue(40)));
+
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_EQ(kOrigin1.spec(), origin.spec());
+
+ // Test that unlimited origins are exluded from eviction, but
+ // protected origins are not excluded.
+ scoped_refptr<MockSpecialStoragePolicy> policy(
+ new MockSpecialStoragePolicy);
+ policy->AddUnlimited(kOrigin1);
+ policy->AddProtected(kOrigin2);
+ EXPECT_TRUE(db.GetLRUOrigin(
+ kStorageTypeTemporary, exceptions, policy.get(), &origin));
+ EXPECT_EQ(kOrigin2.spec(), origin.spec());
+
+ // Test that durable origins are excluded from eviction.
+ policy->AddDurable(kOrigin2);
+ EXPECT_TRUE(db.GetLRUOrigin(
+ kStorageTypeTemporary, exceptions, policy.get(), &origin));
+ EXPECT_EQ(kOrigin3.spec(), origin.spec());
+
+ exceptions.insert(kOrigin1);
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_EQ(kOrigin2.spec(), origin.spec());
+
+ exceptions.insert(kOrigin2);
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_EQ(kOrigin3.spec(), origin.spec());
+
+ exceptions.insert(kOrigin3);
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_TRUE(origin.is_empty());
+
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::Now()));
+
+ // Delete origin/type last access time information.
+ EXPECT_TRUE(db.DeleteOriginInfo(kOrigin3, kStorageTypeTemporary));
+
+ // Querying again to see if the deletion has worked.
+ exceptions.clear();
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_EQ(kOrigin2.spec(), origin.spec());
+
+ exceptions.insert(kOrigin1);
+ exceptions.insert(kOrigin2);
+ EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions,
+ NULL, &origin));
+ EXPECT_TRUE(origin.is_empty());
+ }
+
+ void OriginLastModifiedSince(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ std::set<GURL> origins;
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time()));
+ EXPECT_TRUE(origins.empty());
+
+ const GURL kOrigin1("http://a/");
+ const GURL kOrigin2("http://b/");
+ const GURL kOrigin3("http://c/");
+
+ // Report last mod time for the test origins.
+ EXPECT_TRUE(db.SetOriginLastModifiedTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(0)));
+ EXPECT_TRUE(db.SetOriginLastModifiedTime(
+ kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(10)));
+ EXPECT_TRUE(db.SetOriginLastModifiedTime(
+ kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(20)));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time()));
+ EXPECT_EQ(3U, origins.size());
+ EXPECT_EQ(1U, origins.count(kOrigin1));
+ EXPECT_EQ(1U, origins.count(kOrigin2));
+ EXPECT_EQ(1U, origins.count(kOrigin3));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time::FromInternalValue(5)));
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_EQ(0U, origins.count(kOrigin1));
+ EXPECT_EQ(1U, origins.count(kOrigin2));
+ EXPECT_EQ(1U, origins.count(kOrigin3));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time::FromInternalValue(15)));
+ EXPECT_EQ(1U, origins.size());
+ EXPECT_EQ(0U, origins.count(kOrigin1));
+ EXPECT_EQ(0U, origins.count(kOrigin2));
+ EXPECT_EQ(1U, origins.count(kOrigin3));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time::FromInternalValue(25)));
+ EXPECT_TRUE(origins.empty());
+
+ // Update origin1's mod time but for persistent storage.
+ EXPECT_TRUE(db.SetOriginLastModifiedTime(
+ kOrigin1, kStorageTypePersistent, base::Time::FromInternalValue(30)));
+
+ // Must have no effects on temporary origins info.
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypeTemporary, &origins, base::Time::FromInternalValue(5)));
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_EQ(0U, origins.count(kOrigin1));
+ EXPECT_EQ(1U, origins.count(kOrigin2));
+ EXPECT_EQ(1U, origins.count(kOrigin3));
+
+ // One more update for persistent origin2.
+ EXPECT_TRUE(db.SetOriginLastModifiedTime(
+ kOrigin2, kStorageTypePersistent, base::Time::FromInternalValue(40)));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypePersistent, &origins, base::Time::FromInternalValue(25)));
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_EQ(1U, origins.count(kOrigin1));
+ EXPECT_EQ(1U, origins.count(kOrigin2));
+ EXPECT_EQ(0U, origins.count(kOrigin3));
+
+ EXPECT_TRUE(db.GetOriginsModifiedSince(
+ kStorageTypePersistent, &origins, base::Time::FromInternalValue(35)));
+ EXPECT_EQ(1U, origins.size());
+ EXPECT_EQ(0U, origins.count(kOrigin1));
+ EXPECT_EQ(1U, origins.count(kOrigin2));
+ EXPECT_EQ(0U, origins.count(kOrigin3));
+ }
+
+ void OriginLastEvicted(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ const GURL kOrigin1("http://a/");
+ const GURL kOrigin2("http://b/");
+ const GURL kOrigin3("http://c/");
+
+ base::Time last_eviction_time;
+ EXPECT_FALSE(db.GetOriginLastEvictionTime(kOrigin1, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time(), last_eviction_time);
+
+ // Report last eviction time for the test origins.
+ EXPECT_TRUE(db.SetOriginLastEvictionTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(10)));
+ EXPECT_TRUE(db.SetOriginLastEvictionTime(
+ kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(20)));
+ EXPECT_TRUE(db.SetOriginLastEvictionTime(
+ kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(30)));
+
+ EXPECT_TRUE(db.GetOriginLastEvictionTime(kOrigin1, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time::FromInternalValue(10), last_eviction_time);
+ EXPECT_TRUE(db.GetOriginLastEvictionTime(kOrigin2, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time::FromInternalValue(20), last_eviction_time);
+ EXPECT_TRUE(db.GetOriginLastEvictionTime(kOrigin3, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time::FromInternalValue(30), last_eviction_time);
+
+ // Delete last eviction times for the test origins.
+ EXPECT_TRUE(
+ db.DeleteOriginLastEvictionTime(kOrigin1, kStorageTypeTemporary));
+ EXPECT_TRUE(
+ db.DeleteOriginLastEvictionTime(kOrigin2, kStorageTypeTemporary));
+ EXPECT_TRUE(
+ db.DeleteOriginLastEvictionTime(kOrigin3, kStorageTypeTemporary));
+
+ last_eviction_time = base::Time();
+ EXPECT_FALSE(db.GetOriginLastEvictionTime(kOrigin1, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time(), last_eviction_time);
+ EXPECT_FALSE(db.GetOriginLastEvictionTime(kOrigin2, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time(), last_eviction_time);
+ EXPECT_FALSE(db.GetOriginLastEvictionTime(kOrigin3, kStorageTypeTemporary,
+ &last_eviction_time));
+ EXPECT_EQ(base::Time(), last_eviction_time);
+
+ // Deleting an origin that is not present should not fail.
+ EXPECT_TRUE(db.DeleteOriginLastEvictionTime(GURL("http://notpresent.com"),
+ kStorageTypeTemporary));
+ }
+
+ void RegisterInitialOriginInfo(const base::FilePath& kDbFile) {
+ QuotaDatabase db(kDbFile);
+
+ const GURL kOrigins[] = {
+ GURL("http://a/"),
+ GURL("http://b/"),
+ GURL("http://c/") };
+ std::set<GURL> origins(kOrigins, kOrigins + arraysize(kOrigins));
+
+ EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary));
+
+ QuotaDatabase::OriginInfoTableEntry info;
+ info.used_count = -1;
+ EXPECT_TRUE(db.GetOriginInfo(
+ GURL("http://a/"), kStorageTypeTemporary, &info));
+ EXPECT_EQ(0, info.used_count);
+
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ GURL("http://a/"), kStorageTypeTemporary,
+ base::Time::FromDoubleT(1.0)));
+ info.used_count = -1;
+ EXPECT_TRUE(db.GetOriginInfo(
+ GURL("http://a/"), kStorageTypeTemporary, &info));
+ EXPECT_EQ(1, info.used_count);
+
+ EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary));
+
+ info.used_count = -1;
+ EXPECT_TRUE(db.GetOriginInfo(
+ GURL("http://a/"), kStorageTypeTemporary, &info));
+ EXPECT_EQ(1, info.used_count);
+ }
+
+ template <typename EntryType>
+ struct EntryVerifier {
+ std::set<EntryType> table;
+
+ template <typename Iterator>
+ EntryVerifier(Iterator itr, Iterator end)
+ : table(itr, end) {}
+
+ bool Run(const EntryType& entry) {
+ EXPECT_EQ(1u, table.erase(entry));
+ return true;
+ }
+ };
+
+ void DumpQuotaTable(const base::FilePath& kDbFile) {
+ QuotaTableEntry kTableEntries[] = {
+ QuotaTableEntry("http://go/", kStorageTypeTemporary, 1),
+ QuotaTableEntry("http://oo/", kStorageTypeTemporary, 2),
+ QuotaTableEntry("http://gle/", kStorageTypePersistent, 3)
+ };
+ QuotaTableEntry* begin = kTableEntries;
+ QuotaTableEntry* end = kTableEntries + arraysize(kTableEntries);
+
+ QuotaDatabase db(kDbFile);
+ EXPECT_TRUE(db.LazyOpen(true));
+ AssignQuotaTable(db.db_.get(), begin, end);
+ db.Commit();
+
+ typedef EntryVerifier<QuotaTableEntry> Verifier;
+ Verifier verifier(begin, end);
+ EXPECT_TRUE(db.DumpQuotaTable(
+ base::Bind(&Verifier::Run, base::Unretained(&verifier))));
+ EXPECT_TRUE(verifier.table.empty());
+ }
+
+ void DumpOriginInfoTable(const base::FilePath& kDbFile) {
+ base::Time now(base::Time::Now());
+ typedef QuotaDatabase::OriginInfoTableEntry Entry;
+ Entry kTableEntries[] = {
+ Entry(GURL("http://go/"), kStorageTypeTemporary, 2147483647, now, now),
+ Entry(GURL("http://oo/"), kStorageTypeTemporary, 0, now, now),
+ Entry(GURL("http://gle/"), kStorageTypeTemporary, 1, now, now),
+ };
+ Entry* begin = kTableEntries;
+ Entry* end = kTableEntries + arraysize(kTableEntries);
+
+ QuotaDatabase db(kDbFile);
+ EXPECT_TRUE(db.LazyOpen(true));
+ AssignOriginInfoTable(db.db_.get(), begin, end);
+ db.Commit();
+
+ typedef EntryVerifier<Entry> Verifier;
+ Verifier verifier(begin, end);
+ EXPECT_TRUE(db.DumpOriginInfoTable(
+ base::Bind(&Verifier::Run, base::Unretained(&verifier))));
+ EXPECT_TRUE(verifier.table.empty());
+ }
+
+ void GetOriginInfo(const base::FilePath& kDbFile) {
+ const GURL kOrigin = GURL("http://go/");
+ typedef QuotaDatabase::OriginInfoTableEntry Entry;
+ Entry kTableEntries[] = {
+ Entry(kOrigin, kStorageTypeTemporary, 100, base::Time(), base::Time())};
+ Entry* begin = kTableEntries;
+ Entry* end = kTableEntries + arraysize(kTableEntries);
+
+ QuotaDatabase db(kDbFile);
+ EXPECT_TRUE(db.LazyOpen(true));
+ AssignOriginInfoTable(db.db_.get(), begin, end);
+ db.Commit();
+
+ {
+ Entry entry;
+ EXPECT_TRUE(db.GetOriginInfo(kOrigin, kStorageTypeTemporary, &entry));
+ EXPECT_EQ(kTableEntries[0].type, entry.type);
+ EXPECT_EQ(kTableEntries[0].origin, entry.origin);
+ EXPECT_EQ(kTableEntries[0].used_count, entry.used_count);
+ EXPECT_EQ(kTableEntries[0].last_access_time, entry.last_access_time);
+ EXPECT_EQ(kTableEntries[0].last_modified_time, entry.last_modified_time);
+ }
+
+ {
+ Entry entry;
+ EXPECT_FALSE(db.GetOriginInfo(GURL("http://notpresent.org/"),
+ kStorageTypeTemporary, &entry));
+ }
+ }
+
+ private:
+ template <typename Iterator>
+ void AssignQuotaTable(sql::Connection* db, Iterator itr, Iterator end) {
+ ASSERT_NE(db, (sql::Connection*)NULL);
+ for (; itr != end; ++itr) {
+ const char* kSql =
+ "INSERT INTO HostQuotaTable"
+ " (host, type, quota)"
+ " VALUES (?, ?, ?)";
+ sql::Statement statement;
+ statement.Assign(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+ ASSERT_TRUE(statement.is_valid());
+
+ statement.BindString(0, itr->host);
+ statement.BindInt(1, static_cast<int>(itr->type));
+ statement.BindInt64(2, itr->quota);
+ EXPECT_TRUE(statement.Run());
+ }
+ }
+
+ template <typename Iterator>
+ void AssignOriginInfoTable(sql::Connection* db, Iterator itr, Iterator end) {
+ ASSERT_NE(db, (sql::Connection*)NULL);
+ for (; itr != end; ++itr) {
+ const char* kSql =
+ "INSERT INTO OriginInfoTable"
+ " (origin, type, used_count, last_access_time, last_modified_time)"
+ " VALUES (?, ?, ?, ?, ?)";
+ sql::Statement statement;
+ statement.Assign(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+ ASSERT_TRUE(statement.is_valid());
+
+ statement.BindString(0, itr->origin.spec());
+ statement.BindInt(1, static_cast<int>(itr->type));
+ statement.BindInt(2, itr->used_count);
+ statement.BindInt64(3, itr->last_access_time.ToInternalValue());
+ statement.BindInt64(4, itr->last_modified_time.ToInternalValue());
+ EXPECT_TRUE(statement.Run());
+ }
+ }
+
+ bool OpenDatabase(sql::Connection* db, const base::FilePath& kDbFile) {
+ if (kDbFile.empty()) {
+ return db->OpenInMemory();
+ }
+ if (!base::CreateDirectory(kDbFile.DirName()))
+ return false;
+ if (!db->Open(kDbFile))
+ return false;
+ db->Preload();
+ return true;
+ }
+
+ // Create V2 database and populate some data.
+ void CreateV2Database(
+ const base::FilePath& kDbFile,
+ const QuotaTableEntry* entries,
+ size_t entries_size) {
+ std::unique_ptr<sql::Connection> db(new sql::Connection);
+ std::unique_ptr<sql::MetaTable> meta_table(new sql::MetaTable);
+
+ // V2 schema definitions.
+ static const int kCurrentVersion = 2;
+ static const int kCompatibleVersion = 2;
+ static const char kHostQuotaTable[] = "HostQuotaTable";
+ static const char kOriginLastAccessTable[] = "OriginLastAccessTable";
+ static const QuotaDatabase::TableSchema kTables[] = {
+ { kHostQuotaTable,
+ "(host TEXT NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " quota INTEGER,"
+ " UNIQUE(host, type))" },
+ { kOriginLastAccessTable,
+ "(origin TEXT NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " used_count INTEGER,"
+ " last_access_time INTEGER,"
+ " UNIQUE(origin, type))" },
+ };
+ static const QuotaDatabase::IndexSchema kIndexes[] = {
+ { "HostIndex",
+ kHostQuotaTable,
+ "(host)",
+ false },
+ { "OriginLastAccessIndex",
+ kOriginLastAccessTable,
+ "(origin, last_access_time)",
+ false },
+ };
+
+ ASSERT_TRUE(OpenDatabase(db.get(), kDbFile));
+ EXPECT_TRUE(QuotaDatabase::CreateSchema(
+ db.get(), meta_table.get(),
+ kCurrentVersion, kCompatibleVersion,
+ kTables, arraysize(kTables),
+ kIndexes, arraysize(kIndexes)));
+
+ // V2 and V3 QuotaTable are compatible, so we can simply use
+ // AssignQuotaTable to poplulate v2 database here.
+ db->BeginTransaction();
+ AssignQuotaTable(db.get(), entries, entries + entries_size);
+ db->CommitTransaction();
+ }
+
+ base::MessageLoop message_loop_;
+};
+
+TEST_F(QuotaDatabaseTest, LazyOpen) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ LazyOpen(kDbFile);
+ LazyOpen(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, UpgradeSchema) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ UpgradeSchemaV2toV5(kDbFile);
+}
+
+TEST_F(QuotaDatabaseTest, HostQuota) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ HostQuota(kDbFile);
+ HostQuota(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, GlobalQuota) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ GlobalQuota(kDbFile);
+ GlobalQuota(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, OriginLastAccessTimeLRU) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ OriginLastAccessTimeLRU(kDbFile);
+ OriginLastAccessTimeLRU(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, OriginLastModifiedSince) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ OriginLastModifiedSince(kDbFile);
+ OriginLastModifiedSince(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, OriginLastEvicted) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ OriginLastEvicted(kDbFile);
+ OriginLastEvicted(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, BootstrapFlag) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ QuotaDatabase db(kDbFile);
+
+ EXPECT_FALSE(db.IsOriginDatabaseBootstrapped());
+ EXPECT_TRUE(db.SetOriginDatabaseBootstrapped(true));
+ EXPECT_TRUE(db.IsOriginDatabaseBootstrapped());
+ EXPECT_TRUE(db.SetOriginDatabaseBootstrapped(false));
+ EXPECT_FALSE(db.IsOriginDatabaseBootstrapped());
+}
+
+TEST_F(QuotaDatabaseTest, RegisterInitialOriginInfo) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ RegisterInitialOriginInfo(kDbFile);
+ RegisterInitialOriginInfo(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, DumpQuotaTable) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ DumpQuotaTable(kDbFile);
+ DumpQuotaTable(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, DumpOriginInfoTable) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ DumpOriginInfoTable(kDbFile);
+ DumpOriginInfoTable(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, GetOriginInfo) {
+ GetOriginInfo(base::FilePath());
+}
+
+TEST_F(QuotaDatabaseTest, OpenCorruptedDatabase) {
+ base::ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+ const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName);
+ LazyOpen(kDbFile);
+ ASSERT_TRUE(sql::test::CorruptSizeInHeader(kDbFile));
+ {
+ sql::test::ScopedErrorExpecter expecter;
+ expecter.ExpectError(SQLITE_CORRUPT);
+ Reopen(kDbFile);
+ EXPECT_TRUE(expecter.SawExpectedErrors());
+ }
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/quota/quota_manager.cc b/chromium/storage/browser/quota/quota_manager.cc
index 7b69183818c..518ca1d6d39 100644
--- a/chromium/storage/browser/quota/quota_manager.cc
+++ b/chromium/storage/browser/quota/quota_manager.cc
@@ -847,7 +847,8 @@ void QuotaManager::GetUsageAndQuotaForWebApps(
}
LazyInitialize();
- bool is_session_only = special_storage_policy_ &&
+ bool is_session_only = type == kStorageTypeTemporary &&
+ special_storage_policy_ &&
special_storage_policy_->IsStorageSessionOnly(origin);
UsageAndQuotaHelper* helper = new UsageAndQuotaHelper(
this, origin, type, IsStorageUnlimited(origin, type), is_session_only,
diff --git a/chromium/storage/browser/quota/quota_manager_proxy.cc b/chromium/storage/browser/quota/quota_manager_proxy.cc
index b67cfb9ce05..6f8b15f2adf 100644
--- a/chromium/storage/browser/quota/quota_manager_proxy.cc
+++ b/chromium/storage/browser/quota/quota_manager_proxy.cc
@@ -24,7 +24,7 @@ void DidGetUsageAndQuota(
QuotaStatusCode status,
int64_t usage,
int64_t quota) {
- if (!original_task_runner->RunsTasksOnCurrentThread()) {
+ if (!original_task_runner->RunsTasksInCurrentSequence()) {
original_task_runner->PostTask(
FROM_HERE, base::Bind(&DidGetUsageAndQuota,
base::RetainedRef(original_task_runner), callback,
diff --git a/chromium/storage/browser/quota/quota_manager_unittest.cc b/chromium/storage/browser/quota/quota_manager_unittest.cc
new file mode 100644
index 00000000000..816aa5c25c2
--- /dev/null
+++ b/chromium/storage/browser/quota/quota_manager_unittest.cc
@@ -0,0 +1,2271 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/sys_info.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "storage/browser/quota/quota_database.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/mock_storage_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::kQuotaErrorAbort;
+using storage::kQuotaErrorInvalidModification;
+using storage::kQuotaErrorNotSupported;
+using storage::kQuotaStatusOk;
+using storage::kQuotaStatusUnknown;
+using storage::kStorageTypePersistent;
+using storage::kStorageTypeSyncable;
+using storage::kStorageTypeTemporary;
+using storage::kStorageTypeUnknown;
+using storage::QuotaClient;
+using storage::QuotaManager;
+using storage::QuotaStatusCode;
+using storage::StorageType;
+using storage::UsageInfo;
+using storage::UsageInfoEntries;
+
+namespace content {
+
+namespace {
+
+// For shorter names.
+const StorageType kTemp = kStorageTypeTemporary;
+const StorageType kPerm = kStorageTypePersistent;
+const StorageType kSync = kStorageTypeSyncable;
+
+const int kAllClients = QuotaClient::kAllClientsMask;
+
+// Values in bytes.
+const int64_t kAvailableSpaceForApp = 13377331U;
+const int64_t kMustRemainAvailableForSystem = kAvailableSpaceForApp / 2;
+const int64_t kDefaultPoolSize = 1000;
+const int64_t kDefaultPerHostQuota = 200;
+
+const GURL kTestEvictionOrigin = GURL("http://test.eviction.policy/result");
+
+// Returns a deterministic value for the amount of available disk space.
+int64_t GetAvailableDiskSpaceForTest() {
+ return kAvailableSpaceForApp + kMustRemainAvailableForSystem;
+}
+
+std::tuple<int64_t, int64_t> GetVolumeInfoForTests(
+ const base::FilePath& unused) {
+ int64_t available = static_cast<uint64_t>(GetAvailableDiskSpaceForTest());
+ int64_t total = available * 2;
+ return std::make_tuple(total, available);
+}
+
+} // namespace
+
+class QuotaManagerTest : public testing::Test {
+ protected:
+ typedef QuotaManager::QuotaTableEntry QuotaTableEntry;
+ typedef QuotaManager::QuotaTableEntries QuotaTableEntries;
+ typedef QuotaManager::OriginInfoTableEntries OriginInfoTableEntries;
+
+ public:
+ QuotaManagerTest()
+ : mock_time_counter_(0),
+ weak_factory_(this) {
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ mock_special_storage_policy_ = new MockSpecialStoragePolicy;
+ ResetQuotaManager(false /* is_incognito */);
+ }
+
+ void TearDown() override {
+ // Make sure the quota manager cleans up correctly.
+ quota_manager_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ void ResetQuotaManager(bool is_incognito) {
+ quota_manager_ = new QuotaManager(is_incognito, data_dir_.GetPath(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ mock_special_storage_policy_.get(),
+ storage::GetQuotaSettingsFunc());
+ SetQuotaSettings(kDefaultPoolSize, kDefaultPerHostQuota,
+ is_incognito ? INT64_C(0) : kMustRemainAvailableForSystem);
+
+ // Don't (automatically) start the eviction for testing.
+ quota_manager_->eviction_disabled_ = true;
+ // Don't query the hard disk for remaining capacity.
+ quota_manager_->get_volume_info_fn_= &GetVolumeInfoForTests;
+ additional_callback_count_ = 0;
+ }
+
+ MockStorageClient* CreateClient(
+ const MockOriginData* mock_data,
+ size_t mock_data_size,
+ QuotaClient::ID id) {
+ return new MockStorageClient(quota_manager_->proxy(),
+ mock_data, id, mock_data_size);
+ }
+
+ void RegisterClient(MockStorageClient* client) {
+ quota_manager_->proxy()->RegisterClient(client);
+ }
+
+ void GetUsageInfo() {
+ usage_info_.clear();
+ quota_manager_->GetUsageInfo(
+ base::Bind(&QuotaManagerTest::DidGetUsageInfo,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetUsageAndQuotaForWebApps(const GURL& origin,
+ StorageType type) {
+ quota_status_ = kQuotaStatusUnknown;
+ usage_ = -1;
+ quota_ = -1;
+ quota_manager_->GetUsageAndQuotaForWebApps(
+ origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetUsageAndQuotaForStorageClient(const GURL& origin,
+ StorageType type) {
+ quota_status_ = kQuotaStatusUnknown;
+ usage_ = -1;
+ quota_ = -1;
+ quota_manager_->GetUsageAndQuota(
+ origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void SetQuotaSettings(int64_t pool_size,
+ int64_t per_host_quota,
+ int64_t must_remain_available) {
+ storage::QuotaSettings settings;
+ settings.pool_size = pool_size;
+ settings.per_host_quota = per_host_quota;
+ settings.session_only_per_host_quota =
+ (per_host_quota > 0) ? (per_host_quota - 1) : 0;
+ settings.must_remain_available = must_remain_available;
+ settings.refresh_interval = base::TimeDelta::Max();
+ quota_manager_->SetQuotaSettings(settings);
+ }
+
+ void GetPersistentHostQuota(const std::string& host) {
+ quota_status_ = kQuotaStatusUnknown;
+ quota_ = -1;
+ quota_manager_->GetPersistentHostQuota(
+ host,
+ base::Bind(&QuotaManagerTest::DidGetHostQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void SetPersistentHostQuota(const std::string& host, int64_t new_quota) {
+ quota_status_ = kQuotaStatusUnknown;
+ quota_ = -1;
+ quota_manager_->SetPersistentHostQuota(
+ host, new_quota,
+ base::Bind(&QuotaManagerTest::DidGetHostQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetGlobalUsage(StorageType type) {
+ usage_ = -1;
+ unlimited_usage_ = -1;
+ quota_manager_->GetGlobalUsage(
+ type,
+ base::Bind(&QuotaManagerTest::DidGetGlobalUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetHostUsage(const std::string& host, StorageType type) {
+ usage_ = -1;
+ quota_manager_->GetHostUsage(
+ host, type,
+ base::Bind(&QuotaManagerTest::DidGetHostUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void RunAdditionalUsageAndQuotaTask(const GURL& origin, StorageType type) {
+ quota_manager_->GetUsageAndQuota(
+ origin, type,
+ base::Bind(&QuotaManagerTest::DidGetUsageAndQuotaAdditional,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DeleteClientOriginData(QuotaClient* client,
+ const GURL& origin,
+ StorageType type) {
+ DCHECK(client);
+ quota_status_ = kQuotaStatusUnknown;
+ client->DeleteOriginData(
+ origin, type,
+ base::Bind(&QuotaManagerTest::StatusCallback,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void EvictOriginData(const GURL& origin,
+ StorageType type) {
+ quota_status_ = kQuotaStatusUnknown;
+ quota_manager_->EvictOriginData(
+ origin, type,
+ base::Bind(&QuotaManagerTest::StatusCallback,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DeleteOriginData(const GURL& origin,
+ StorageType type,
+ int quota_client_mask) {
+ quota_status_ = kQuotaStatusUnknown;
+ quota_manager_->DeleteOriginData(
+ origin, type, quota_client_mask,
+ base::Bind(&QuotaManagerTest::StatusCallback,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DeleteHostData(const std::string& host,
+ StorageType type,
+ int quota_client_mask) {
+ quota_status_ = kQuotaStatusUnknown;
+ quota_manager_->DeleteHostData(
+ host, type, quota_client_mask,
+ base::Bind(&QuotaManagerTest::StatusCallback,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetStorageCapacity() {
+ available_space_ = -1;
+ total_space_ = -1;
+ quota_manager_->GetStorageCapacity(base::Bind(
+ &QuotaManagerTest::DidGetStorageCapacity, weak_factory_.GetWeakPtr()));
+ }
+
+ void GetEvictionRoundInfo() {
+ quota_status_ = kQuotaStatusUnknown;
+ settings_ = storage::QuotaSettings();
+ available_space_ = -1;
+ total_space_ = -1;
+ usage_ = -1;
+ quota_manager_->GetEvictionRoundInfo(
+ base::Bind(&QuotaManagerTest::DidGetEvictionRoundInfo,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void GetCachedOrigins(StorageType type, std::set<GURL>* origins) {
+ ASSERT_TRUE(origins != NULL);
+ origins->clear();
+ quota_manager_->GetCachedOrigins(type, origins);
+ }
+
+ void NotifyStorageAccessed(QuotaClient* client,
+ const GURL& origin,
+ StorageType type) {
+ DCHECK(client);
+ quota_manager_->NotifyStorageAccessedInternal(
+ client->id(), origin, type, IncrementMockTime());
+ }
+
+ void DeleteOriginFromDatabase(const GURL& origin, StorageType type) {
+ quota_manager_->DeleteOriginFromDatabase(origin, type, false);
+ }
+
+ void GetEvictionOrigin(StorageType type) {
+ eviction_origin_ = GURL();
+ // The quota manager's default eviction policy is to use an LRU eviction
+ // policy.
+ quota_manager_->GetEvictionOrigin(
+ type, std::set<GURL>(), 0,
+ base::Bind(&QuotaManagerTest::DidGetEvictionOrigin,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void NotifyOriginInUse(const GURL& origin) {
+ quota_manager_->NotifyOriginInUse(origin);
+ }
+
+ void NotifyOriginNoLongerInUse(const GURL& origin) {
+ quota_manager_->NotifyOriginNoLongerInUse(origin);
+ }
+
+ void GetOriginsModifiedSince(StorageType type, base::Time modified_since) {
+ modified_origins_.clear();
+ modified_origins_type_ = kStorageTypeUnknown;
+ quota_manager_->GetOriginsModifiedSince(
+ type, modified_since,
+ base::Bind(&QuotaManagerTest::DidGetModifiedOrigins,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DumpQuotaTable() {
+ quota_entries_.clear();
+ quota_manager_->DumpQuotaTable(
+ base::Bind(&QuotaManagerTest::DidDumpQuotaTable,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DumpOriginInfoTable() {
+ origin_info_entries_.clear();
+ quota_manager_->DumpOriginInfoTable(
+ base::Bind(&QuotaManagerTest::DidDumpOriginInfoTable,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DidGetUsageInfo(const UsageInfoEntries& entries) {
+ usage_info_.insert(usage_info_.begin(), entries.begin(), entries.end());
+ }
+
+ void DidGetUsageAndQuota(QuotaStatusCode status,
+ int64_t usage,
+ int64_t quota) {
+ quota_status_ = status;
+ usage_ = usage;
+ quota_ = quota;
+ }
+
+ void DidGetQuota(QuotaStatusCode status, int64_t quota) {
+ quota_status_ = status;
+ quota_ = quota;
+ }
+
+ void DidGetStorageCapacity(int64_t total_space, int64_t available_space) {
+ total_space_ = total_space;
+ available_space_ = available_space;
+ }
+
+ void DidGetHostQuota(QuotaStatusCode status, int64_t quota) {
+ quota_status_ = status;
+ quota_ = quota;
+ }
+
+ void DidGetGlobalUsage(int64_t usage, int64_t unlimited_usage) {
+ usage_ = usage;
+ unlimited_usage_ = unlimited_usage;
+ }
+
+ void DidGetHostUsage(int64_t usage) { usage_ = usage; }
+
+ void StatusCallback(QuotaStatusCode status) {
+ ++status_callback_count_;
+ quota_status_ = status;
+ }
+
+ void DidGetEvictionRoundInfo(QuotaStatusCode status,
+ const storage::QuotaSettings& settings,
+ int64_t available_space,
+ int64_t total_space,
+ int64_t global_usage,
+ bool global_usage_is_complete) {
+ quota_status_ = status;
+ settings_ = settings;
+ available_space_ = available_space;
+ total_space_ = total_space;
+ usage_ = global_usage;
+ }
+
+ void DidGetEvictionOrigin(const GURL& origin) {
+ eviction_origin_ = origin;
+ }
+
+ void DidGetModifiedOrigins(const std::set<GURL>& origins, StorageType type) {
+ modified_origins_ = origins;
+ modified_origins_type_ = type;
+ }
+
+ void DidDumpQuotaTable(const QuotaTableEntries& entries) {
+ quota_entries_ = entries;
+ }
+
+ void DidDumpOriginInfoTable(const OriginInfoTableEntries& entries) {
+ origin_info_entries_ = entries;
+ }
+
+ void GetUsage_WithModifyTestBody(const StorageType type);
+
+ void set_additional_callback_count(int c) { additional_callback_count_ = c; }
+ int additional_callback_count() const { return additional_callback_count_; }
+ void DidGetUsageAndQuotaAdditional(QuotaStatusCode status,
+ int64_t usage,
+ int64_t quota) {
+ ++additional_callback_count_;
+ }
+
+ QuotaManager* quota_manager() const { return quota_manager_.get(); }
+ void set_quota_manager(QuotaManager* quota_manager) {
+ quota_manager_ = quota_manager;
+ }
+
+ MockSpecialStoragePolicy* mock_special_storage_policy() const {
+ return mock_special_storage_policy_.get();
+ }
+
+ QuotaStatusCode status() const { return quota_status_; }
+ const UsageInfoEntries& usage_info() const { return usage_info_; }
+ int64_t usage() const { return usage_; }
+ int64_t limited_usage() const { return limited_usage_; }
+ int64_t unlimited_usage() const { return unlimited_usage_; }
+ int64_t quota() const { return quota_; }
+ int64_t total_space() const { return total_space_; }
+ int64_t available_space() const { return available_space_; }
+ const GURL& eviction_origin() const { return eviction_origin_; }
+ const std::set<GURL>& modified_origins() const { return modified_origins_; }
+ StorageType modified_origins_type() const { return modified_origins_type_; }
+ const QuotaTableEntries& quota_entries() const { return quota_entries_; }
+ const OriginInfoTableEntries& origin_info_entries() const {
+ return origin_info_entries_;
+ }
+ const storage::QuotaSettings& settings() const { return settings_; }
+ base::FilePath profile_path() const { return data_dir_.GetPath(); }
+ int status_callback_count() const { return status_callback_count_; }
+ void reset_status_callback_count() { status_callback_count_ = 0; }
+
+ private:
+ base::Time IncrementMockTime() {
+ ++mock_time_counter_;
+ return base::Time::FromDoubleT(mock_time_counter_ * 10.0);
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::ScopedTempDir data_dir_;
+
+ scoped_refptr<QuotaManager> quota_manager_;
+ scoped_refptr<MockSpecialStoragePolicy> mock_special_storage_policy_;
+
+ QuotaStatusCode quota_status_;
+ UsageInfoEntries usage_info_;
+ int64_t usage_;
+ int64_t limited_usage_;
+ int64_t unlimited_usage_;
+ int64_t quota_;
+ int64_t total_space_;
+ int64_t available_space_;
+ GURL eviction_origin_;
+ std::set<GURL> modified_origins_;
+ StorageType modified_origins_type_;
+ QuotaTableEntries quota_entries_;
+ OriginInfoTableEntries origin_info_entries_;
+ storage::QuotaSettings settings_;
+ int status_callback_count_;
+
+ int additional_callback_count_;
+
+ int mock_time_counter_;
+
+ base::WeakPtrFactory<QuotaManagerTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaManagerTest);
+};
+
+TEST_F(QuotaManagerTest, GetUsageInfo) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com:8080/", kTemp, 15 },
+ { "http://bar.com/", kTemp, 20 },
+ { "http://bar.com/", kPerm, 50 },
+ };
+ static const MockOriginData kData2[] = {
+ { "https://foo.com/", kTemp, 30 },
+ { "https://foo.com:8081/", kTemp, 35 },
+ { "http://bar.com/", kPerm, 40 },
+ { "http://example.com/", kPerm, 40 },
+ };
+ RegisterClient(CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem));
+ RegisterClient(CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kDatabase));
+
+ GetUsageInfo();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(4U, usage_info().size());
+ for (size_t i = 0; i < usage_info().size(); ++i) {
+ const UsageInfo& info = usage_info()[i];
+ if (info.host == "foo.com" && info.type == kTemp) {
+ EXPECT_EQ(10 + 15 + 30 + 35, info.usage);
+ } else if (info.host == "bar.com" && info.type == kTemp) {
+ EXPECT_EQ(20, info.usage);
+ } else if (info.host == "bar.com" && info.type == kPerm) {
+ EXPECT_EQ(50 + 40, info.usage);
+ } else if (info.host == "example.com" && info.type == kPerm) {
+ EXPECT_EQ(40, info.usage);
+ } else {
+ ADD_FAILURE()
+ << "Unexpected host, type: " << info.host << ", " << info.type;
+ }
+ }
+}
+
+TEST_F(QuotaManagerTest, GetUsageAndQuota_Simple) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com/", kPerm, 80 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(80, usage());
+ EXPECT_EQ(0, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_LE(0, quota());
+ int64_t quota_returned_for_foo = quota();
+
+ GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(quota_returned_for_foo, quota());
+}
+
+TEST_F(QuotaManagerTest, GetUsage_NoClient) {
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(0, unlimited_usage());
+}
+
+TEST_F(QuotaManagerTest, GetUsage_EmptyClient) {
+ RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(0, unlimited_usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com:8080/", kTemp, 20 },
+ { "http://bar.com/", kTemp, 5 },
+ { "https://bar.com/", kTemp, 7 },
+ { "http://baz.com/", kTemp, 30 },
+ { "http://foo.com/", kPerm, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ // This time explicitly sets a temporary global quota.
+ const int kPoolSize = 100;
+ const int kPerHostQuota = 20;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+
+ // The host's quota should be its full portion of the global quota
+ // since there's plenty of diskspace.
+ EXPECT_EQ(kPerHostQuota, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(5 + 7, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+}
+
+TEST_F(QuotaManagerTest, GetUsage_MultipleClients) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://bar.com/", kTemp, 2 },
+ { "http://bar.com/", kPerm, 4 },
+ { "http://unlimited/", kPerm, 8 },
+ };
+ static const MockOriginData kData2[] = {
+ { "https://foo.com/", kTemp, 128 },
+ { "http://example.com/", kPerm, 256 },
+ { "http://unlimited/", kTemp, 512 },
+ };
+ mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
+ RegisterClient(CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem));
+ RegisterClient(CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kDatabase));
+
+ const int64_t kPoolSize = GetAvailableDiskSpaceForTest();
+ const int64_t kPerHostQuota = kPoolSize / 5;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(1 + 128, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4, usage());
+ EXPECT_EQ(0, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(512, usage());
+ EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(8, usage());
+ EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(1 + 2 + 128 + 512, usage());
+ EXPECT_EQ(512, unlimited_usage());
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4 + 8 + 256, usage());
+ EXPECT_EQ(8, unlimited_usage());
+}
+
+void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) {
+ const MockOriginData data[] = {
+ { "http://foo.com/", type, 10 },
+ { "http://foo.com:1/", type, 20 },
+ };
+ MockStorageClient* client = CreateClient(data, arraysize(data),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+
+ client->ModifyOriginAndNotify(GURL("http://foo.com/"), type, 30);
+ client->ModifyOriginAndNotify(GURL("http://foo.com:1/"), type, -5);
+ client->AddOriginAndNotify(GURL("https://foo.com/"), type, 1);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20 + 30 - 5 + 1, usage());
+ int foo_usage = usage();
+
+ client->AddOriginAndNotify(GURL("http://bar.com/"), type, 40);
+ GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), type);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(40, usage());
+
+ GetGlobalUsage(type);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(foo_usage + 40, usage());
+ EXPECT_EQ(0, unlimited_usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsage_WithModify) {
+ GetUsage_WithModifyTestBody(kTemp);
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com:8080/", kTemp, 20 },
+ { "http://bar.com/", kTemp, 13 },
+ { "http://foo.com/", kPerm, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ const int kPoolSize = 100;
+ const int kPerHostQuota = 20;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+
+ set_additional_callback_count(0);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
+ kTemp);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+ EXPECT_EQ(2, additional_callback_count());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_NukeManager) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com:8080/", kTemp, 20 },
+ { "http://bar.com/", kTemp, 13 },
+ { "http://foo.com/", kPerm, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+ const int kPoolSize = 100;
+ const int kPerHostQuota = 20;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ set_additional_callback_count(0);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
+ kTemp);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"),
+ kTemp);
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
+ DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);
+
+ // Nuke before waiting for callbacks.
+ set_quota_manager(NULL);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaErrorAbort, status());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Overbudget) {
+ static const MockOriginData kData[] = {
+ { "http://usage1/", kTemp, 1 },
+ { "http://usage10/", kTemp, 10 },
+ { "http://usage200/", kTemp, 200 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+ const int kPoolSize = 100;
+ const int kPerHostQuota = 20;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ // Provided diskspace is not tight, global usage does not affect the
+ // quota calculations for an individual origin, so despite global usage
+ // in excess of our poolsize, we still get the nominal quota value.
+ GetStorageCapacity();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_LE(kMustRemainAvailableForSystem, available_space());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage1/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(1, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_EQ(kPerHostQuota, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage200/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(200, usage());
+ EXPECT_EQ(kPerHostQuota, quota()); // should be clamped to the nominal quota
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Unlimited) {
+ static const MockOriginData kData[] = {
+ { "http://usage10/", kTemp, 10 },
+ { "http://usage50/", kTemp, 50 },
+ { "http://unlimited/", kTemp, 4000 },
+ };
+ mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ // Test when not overbugdet.
+ const int kPerHostQuotaFor1000 = 200;
+ SetQuotaSettings(1000, kPerHostQuotaFor1000, kMustRemainAvailableForSystem);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(10 + 50 + 4000, usage());
+ EXPECT_EQ(4000, unlimited_usage());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_EQ(kPerHostQuotaFor1000, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(50, usage());
+ EXPECT_EQ(kPerHostQuotaFor1000, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4000, usage());
+ EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
+
+ GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(QuotaManager::kNoLimit, quota());
+
+ // Test when overbugdet.
+ const int kPerHostQuotaFor100 = 20;
+ SetQuotaSettings(100, kPerHostQuotaFor100, kMustRemainAvailableForSystem);
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(50, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4000, usage());
+ EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
+
+ GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(QuotaManager::kNoLimit, quota());
+
+ // Revoke the unlimited rights and make sure the change is noticed.
+ mock_special_storage_policy()->Reset();
+ mock_special_storage_policy()->NotifyCleared();
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(10 + 50 + 4000, usage());
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(50, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4000, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+
+ GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(4000, usage());
+ EXPECT_EQ(kPerHostQuotaFor100, quota());
+}
+
+TEST_F(QuotaManagerTest, OriginInUse) {
+ const GURL kFooOrigin("http://foo.com/");
+ const GURL kBarOrigin("http://bar.com/");
+
+ EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin));
+ quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 1
+ EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));
+ quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 2
+ EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));
+ quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin); // count of 1
+ EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));
+
+ EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin));
+ quota_manager()->NotifyOriginInUse(kBarOrigin);
+ EXPECT_TRUE(quota_manager()->IsOriginInUse(kBarOrigin));
+ quota_manager()->NotifyOriginNoLongerInUse(kBarOrigin);
+ EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin));
+
+ quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin);
+ EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin));
+}
+
+TEST_F(QuotaManagerTest, GetAndSetPerststentHostQuota) {
+ RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));
+
+ GetPersistentHostQuota("foo.com");
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, quota());
+
+ SetPersistentHostQuota("foo.com", 100);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(100, quota());
+
+ GetPersistentHostQuota("foo.com");
+ SetPersistentHostQuota("foo.com", 200);
+ GetPersistentHostQuota("foo.com");
+ SetPersistentHostQuota("foo.com", QuotaManager::kPerHostPersistentQuotaLimit);
+ GetPersistentHostQuota("foo.com");
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(QuotaManager::kPerHostPersistentQuotaLimit, quota());
+
+ // Persistent quota should be capped at the per-host quota limit.
+ SetPersistentHostQuota("foo.com",
+ QuotaManager::kPerHostPersistentQuotaLimit + 100);
+ GetPersistentHostQuota("foo.com");
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(QuotaManager::kPerHostPersistentQuotaLimit, quota());
+}
+
+TEST_F(QuotaManagerTest, GetAndSetPersistentUsageAndQuota) {
+ RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(0, quota());
+
+ SetPersistentHostQuota("foo.com", 100);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(100, quota());
+
+ // The actual space avaialble is given to 'unlimited' origins as their quota.
+ mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kAvailableSpaceForApp, quota());
+
+ // GetUsageAndQuotaForStorageClient should just return 0 usage and
+ // kNoLimit quota.
+ GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(QuotaManager::kNoLimit, quota());
+}
+
+TEST_F(QuotaManagerTest, GetSyncableQuota) {
+ RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));
+
+ // Pre-condition check: available disk space (for testing) is less than
+ // the default quota for syncable storage.
+ EXPECT_LE(kAvailableSpaceForApp,
+ QuotaManager::kSyncableStorageDefaultHostQuota);
+
+ // For unlimited origins the quota manager should return
+ // kAvailableSpaceForApp as syncable quota (because of the pre-condition).
+ mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
+ GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kSync);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(kAvailableSpaceForApp, quota());
+}
+
+TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_MultiOrigins) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kPerm, 10 },
+ { "http://foo.com:8080/", kPerm, 20 },
+ { "https://foo.com/", kPerm, 13 },
+ { "https://foo.com:8081/", kPerm, 19 },
+ { "http://bar.com/", kPerm, 5 },
+ { "https://bar.com/", kPerm, 7 },
+ { "http://baz.com/", kPerm, 30 },
+ { "http://foo.com/", kTemp, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ SetPersistentHostQuota("foo.com", 100);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20 + 13 + 19, usage());
+ EXPECT_EQ(100, quota());
+}
+
+TEST_F(QuotaManagerTest, GetPersistentUsage_WithModify) {
+ GetUsage_WithModifyTestBody(kPerm);
+}
+
+TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_WithAdditionalTasks) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kPerm, 10 },
+ { "http://foo.com:8080/", kPerm, 20 },
+ { "http://bar.com/", kPerm, 13 },
+ { "http://foo.com/", kTemp, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+ SetPersistentHostQuota("foo.com", 100);
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(100, quota());
+
+ set_additional_callback_count(0);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
+ kPerm);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(2, additional_callback_count());
+}
+
+TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_NukeManager) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kPerm, 10 },
+ { "http://foo.com:8080/", kPerm, 20 },
+ { "http://bar.com/", kPerm, 13 },
+ { "http://foo.com/", kTemp, 40 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+ SetPersistentHostQuota("foo.com", 100);
+
+ set_additional_callback_count(0);
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), kPerm);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);
+
+ // Nuke before waiting for callbacks.
+ set_quota_manager(NULL);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaErrorAbort, status());
+}
+
+TEST_F(QuotaManagerTest, GetUsage_Simple) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kPerm, 1 },
+ { "http://foo.com:1/", kPerm, 20 },
+ { "http://bar.com/", kTemp, 300 },
+ { "https://buz.com/", kTemp, 4000 },
+ { "http://buz.com/", kTemp, 50000 },
+ { "http://bar.com:1/", kPerm, 600000 },
+ { "http://foo.com/", kTemp, 7000000 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 1 + 20 + 600000);
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000);
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 1 + 20);
+
+ GetHostUsage("buz.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 4000 + 50000);
+}
+
+TEST_F(QuotaManagerTest, GetUsage_WithModification) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kPerm, 1 },
+ { "http://foo.com:1/", kPerm, 20 },
+ { "http://bar.com/", kTemp, 300 },
+ { "https://buz.com/", kTemp, 4000 },
+ { "http://buz.com/", kTemp, 50000 },
+ { "http://bar.com:1/", kPerm, 600000 },
+ { "http://foo.com/", kTemp, 7000000 },
+ };
+
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 1 + 20 + 600000);
+ EXPECT_EQ(0, unlimited_usage());
+
+ client->ModifyOriginAndNotify(
+ GURL("http://foo.com/"), kPerm, 80000000);
+
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 1 + 20 + 600000 + 80000000);
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000);
+ EXPECT_EQ(0, unlimited_usage());
+
+ client->ModifyOriginAndNotify(
+ GURL("http://foo.com/"), kTemp, 1);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000 + 1);
+ EXPECT_EQ(0, unlimited_usage());
+
+ GetHostUsage("buz.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 4000 + 50000);
+
+ client->ModifyOriginAndNotify(
+ GURL("http://buz.com/"), kTemp, 900000000);
+
+ GetHostUsage("buz.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(usage(), 4000 + 50000 + 900000000);
+}
+
+TEST_F(QuotaManagerTest, GetUsage_WithDeleteOrigin) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://bar.com/", kTemp, 4000 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_pers = usage();
+
+ DeleteClientOriginData(client, GURL("http://foo.com/"),
+ kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp - 1, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_tmp - 1, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_pers, usage());
+}
+
+TEST_F(QuotaManagerTest, GetStorageCapacity) {
+ GetStorageCapacity();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_LE(0, total_space());
+ EXPECT_LE(0, available_space());
+}
+
+TEST_F(QuotaManagerTest, EvictOriginData) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://bar.com/", kTemp, 4000 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com/", kTemp, 50000 },
+ { "http://foo.com:1/", kTemp, 6000 },
+ { "http://foo.com/", kPerm, 700 },
+ { "https://foo.com/", kTemp, 80 },
+ { "http://bar.com/", kTemp, 9 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_pers = usage();
+
+ for (size_t i = 0; i < arraysize(kData1); ++i)
+ quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
+ GURL(kData1[i].origin), kData1[i].type);
+ for (size_t i = 0; i < arraysize(kData2); ++i)
+ quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
+ GURL(kData2[i].origin), kData2[i].type);
+ base::RunLoop().RunUntilIdle();
+
+ EvictOriginData(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ DumpOriginInfoTable();
+ base::RunLoop().RunUntilIdle();
+
+ typedef OriginInfoTableEntries::const_iterator iterator;
+ for (iterator itr(origin_info_entries().begin()),
+ end(origin_info_entries().end());
+ itr != end; ++itr) {
+ if (itr->type == kTemp)
+ EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
+ }
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp - (1 + 50000), usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_tmp - (1 + 50000), usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_pers, usage());
+}
+
+TEST_F(QuotaManagerTest, EvictOriginDataHistogram) {
+ const GURL kOrigin = GURL("http://foo.com/");
+ static const MockOriginData kData[] = {
+ {"http://foo.com/", kTemp, 1},
+ };
+
+ base::HistogramTester histograms;
+ MockStorageClient* client =
+ CreateClient(kData, arraysize(kData), QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ EvictOriginData(kOrigin, kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ // Ensure used count and time since access are recorded.
+ histograms.ExpectTotalCount(
+ QuotaManager::kEvictedOriginAccessedCountHistogram, 1);
+ histograms.ExpectBucketCount(
+ QuotaManager::kEvictedOriginAccessedCountHistogram, 0, 1);
+ histograms.ExpectTotalCount(
+ QuotaManager::kEvictedOriginDaysSinceAccessHistogram, 1);
+
+ // First eviction has no 'last' time to compare to.
+ histograms.ExpectTotalCount(
+ QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram, 0);
+
+ client->AddOriginAndNotify(kOrigin, kTemp, 100);
+
+ // Change the used count of the origin.
+ quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, GURL(kOrigin),
+ kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ EvictOriginData(kOrigin, kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ // The new used count should be logged.
+ histograms.ExpectTotalCount(
+ QuotaManager::kEvictedOriginAccessedCountHistogram, 2);
+ histograms.ExpectBucketCount(
+ QuotaManager::kEvictedOriginAccessedCountHistogram, 1, 1);
+ histograms.ExpectTotalCount(
+ QuotaManager::kEvictedOriginDaysSinceAccessHistogram, 2);
+
+ // Second eviction should log a 'time between repeated eviction' sample.
+ histograms.ExpectTotalCount(
+ QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram, 1);
+
+ client->AddOriginAndNotify(kOrigin, kTemp, 100);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+
+ DeleteOriginFromDatabase(kOrigin, kTemp);
+
+ // Deletion from non-eviction source should not log a histogram sample.
+ histograms.ExpectTotalCount(
+ QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram, 1);
+}
+
+TEST_F(QuotaManagerTest, EvictOriginDataWithDeletionError) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://bar.com/", kTemp, 4000 },
+ };
+ static const int kNumberOfTemporaryOrigins = 3;
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_pers = usage();
+
+ for (size_t i = 0; i < arraysize(kData); ++i)
+ NotifyStorageAccessed(client, GURL(kData[i].origin), kData[i].type);
+ base::RunLoop().RunUntilIdle();
+
+ client->AddOriginToErrorSet(GURL("http://foo.com/"), kTemp);
+
+ for (int i = 0;
+ i < QuotaManager::kThresholdOfErrorsToBeBlacklisted + 1;
+ ++i) {
+ EvictOriginData(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaErrorInvalidModification, status());
+ }
+
+ DumpOriginInfoTable();
+ base::RunLoop().RunUntilIdle();
+
+ bool found_origin_in_database = false;
+ typedef OriginInfoTableEntries::const_iterator iterator;
+ for (iterator itr(origin_info_entries().begin()),
+ end(origin_info_entries().end());
+ itr != end; ++itr) {
+ if (itr->type == kTemp && itr->origin == "http://foo.com/") {
+ found_origin_in_database = true;
+ break;
+ }
+ }
+ // The origin "http://foo.com/" should be in the database.
+ EXPECT_TRUE(found_origin_in_database);
+
+ for (size_t i = 0; i < kNumberOfTemporaryOrigins - 1; ++i) {
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(eviction_origin().is_empty());
+ // The origin "http://foo.com/" should not be in the LRU list.
+ EXPECT_NE(std::string("http://foo.com/"), eviction_origin().spec());
+ DeleteOriginFromDatabase(eviction_origin(), kTemp);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Now the LRU list must be empty.
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(eviction_origin().is_empty());
+
+ // Deleting origins from the database should not affect the results of the
+ // following checks.
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_tmp, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_pers, usage());
+}
+
+TEST_F(QuotaManagerTest, GetEvictionRoundInfo) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://unlimited/", kTemp, 4000 },
+ };
+
+ mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ const int kPoolSize = 10000000;
+ const int kPerHostQuota = kPoolSize / 5;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
+
+ GetEvictionRoundInfo();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(21, usage());
+ EXPECT_EQ(kPoolSize, settings().pool_size);
+ EXPECT_LE(0, available_space());
+}
+
+TEST_F(QuotaManagerTest, DeleteHostDataSimple) {
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 1 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ int64_t predelete_host_pers = usage();
+
+ DeleteHostData(std::string(), kTemp, kAllClients);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_tmp, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_pers, usage());
+
+ DeleteHostData("foo.com", kTemp, kAllClients);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp - 1, usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_tmp - 1, usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_host_pers, usage());
+}
+
+TEST_F(QuotaManagerTest, DeleteHostDataMultiple) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://bar.com/", kTemp, 4000 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com/", kTemp, 50000 },
+ { "http://foo.com:1/", kTemp, 6000 },
+ { "http://foo.com/", kPerm, 700 },
+ { "https://foo.com/", kTemp, 80 },
+ { "http://bar.com/", kTemp, 9 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ GetHostUsage("bar.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_bar_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_pers = usage();
+
+ GetHostUsage("bar.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_bar_pers = usage();
+
+ reset_status_callback_count();
+ DeleteHostData("foo.com", kTemp, kAllClients);
+ DeleteHostData("bar.com", kTemp, kAllClients);
+ DeleteHostData("foo.com", kTemp, kAllClients);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(3, status_callback_count());
+
+ DumpOriginInfoTable();
+ base::RunLoop().RunUntilIdle();
+
+ typedef OriginInfoTableEntries::const_iterator iterator;
+ for (iterator itr(origin_info_entries().begin()),
+ end(origin_info_entries().end());
+ itr != end; ++itr) {
+ if (itr->type == kTemp) {
+ EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
+ EXPECT_NE(std::string("http://foo.com:1/"), itr->origin.spec());
+ EXPECT_NE(std::string("https://foo.com/"), itr->origin.spec());
+ EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec());
+ }
+ }
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp - (1 + 20 + 4000 + 50000 + 6000 + 80 + 9),
+ usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - (1 + 20 + 50000 + 6000 + 80), usage());
+
+ GetHostUsage("bar.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_pers, usage());
+
+ GetHostUsage("bar.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_bar_pers, usage());
+}
+
+// Single-run DeleteOriginData cases must be well covered by
+// EvictOriginData tests.
+TEST_F(QuotaManagerTest, DeleteOriginDataMultiple) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ { "http://foo.com:1/", kTemp, 20 },
+ { "http://foo.com/", kPerm, 300 },
+ { "http://bar.com/", kTemp, 4000 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com/", kTemp, 50000 },
+ { "http://foo.com:1/", kTemp, 6000 },
+ { "http://foo.com/", kPerm, 700 },
+ { "https://foo.com/", kTemp, 80 },
+ { "http://bar.com/", kTemp, 9 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_global_tmp = usage();
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ GetHostUsage("bar.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_bar_tmp = usage();
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_pers = usage();
+
+ GetHostUsage("bar.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_bar_pers = usage();
+
+ for (size_t i = 0; i < arraysize(kData1); ++i)
+ quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
+ GURL(kData1[i].origin), kData1[i].type);
+ for (size_t i = 0; i < arraysize(kData2); ++i)
+ quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
+ GURL(kData2[i].origin), kData2[i].type);
+ base::RunLoop().RunUntilIdle();
+
+ reset_status_callback_count();
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
+ DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(3, status_callback_count());
+
+ DumpOriginInfoTable();
+ base::RunLoop().RunUntilIdle();
+
+ typedef OriginInfoTableEntries::const_iterator iterator;
+ for (iterator itr(origin_info_entries().begin()),
+ end(origin_info_entries().end());
+ itr != end; ++itr) {
+ if (itr->type == kTemp) {
+ EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
+ EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec());
+ }
+ }
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_global_tmp - (1 + 4000 + 50000 + 9), usage());
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - (1 + 50000), usage());
+
+ GetHostUsage("bar.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage());
+
+ GetHostUsage("foo.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_pers, usage());
+
+ GetHostUsage("bar.com", kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_bar_pers, usage());
+}
+
+TEST_F(QuotaManagerTest, GetCachedOrigins) {
+ static const MockOriginData kData[] = {
+ { "http://a.com/", kTemp, 1 },
+ { "http://a.com:1/", kTemp, 20 },
+ { "http://b.com/", kPerm, 300 },
+ { "http://c.com/", kTemp, 4000 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ // TODO(kinuko): Be careful when we add cache pruner.
+
+ std::set<GURL> origins;
+ GetCachedOrigins(kTemp, &origins);
+ EXPECT_TRUE(origins.empty());
+
+ GetHostUsage("a.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ GetCachedOrigins(kTemp, &origins);
+ EXPECT_EQ(2U, origins.size());
+
+ GetHostUsage("b.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ GetCachedOrigins(kTemp, &origins);
+ EXPECT_EQ(2U, origins.size());
+
+ GetHostUsage("c.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ GetCachedOrigins(kTemp, &origins);
+ EXPECT_EQ(3U, origins.size());
+
+ GetCachedOrigins(kPerm, &origins);
+ EXPECT_TRUE(origins.empty());
+
+ GetGlobalUsage(kTemp);
+ base::RunLoop().RunUntilIdle();
+ GetCachedOrigins(kTemp, &origins);
+ EXPECT_EQ(3U, origins.size());
+
+ for (size_t i = 0; i < arraysize(kData); ++i) {
+ if (kData[i].type == kTemp)
+ EXPECT_TRUE(origins.find(GURL(kData[i].origin)) != origins.end());
+ }
+}
+
+TEST_F(QuotaManagerTest, NotifyAndLRUOrigin) {
+ static const MockOriginData kData[] = {
+ { "http://a.com/", kTemp, 0 },
+ { "http://a.com:1/", kTemp, 0 },
+ { "https://a.com/", kTemp, 0 },
+ { "http://b.com/", kPerm, 0 }, // persistent
+ { "http://c.com/", kTemp, 0 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GURL origin;
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(eviction_origin().is_empty());
+
+ NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("http://a.com/", eviction_origin().spec());
+
+ NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
+ NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
+ NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("http://a.com/", eviction_origin().spec());
+
+ DeleteOriginFromDatabase(eviction_origin(), kTemp);
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("https://a.com/", eviction_origin().spec());
+
+ DeleteOriginFromDatabase(eviction_origin(), kTemp);
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("http://c.com/", eviction_origin().spec());
+}
+
+TEST_F(QuotaManagerTest, GetLRUOriginWithOriginInUse) {
+ static const MockOriginData kData[] = {
+ { "http://a.com/", kTemp, 0 },
+ { "http://a.com:1/", kTemp, 0 },
+ { "https://a.com/", kTemp, 0 },
+ { "http://b.com/", kPerm, 0 }, // persistent
+ { "http://c.com/", kTemp, 0 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GURL origin;
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(eviction_origin().is_empty());
+
+ NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
+ NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
+ NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
+ NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("http://a.com/", eviction_origin().spec());
+
+ // Notify origin http://a.com is in use.
+ NotifyOriginInUse(GURL("http://a.com/"));
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("https://a.com/", eviction_origin().spec());
+
+ // Notify origin https://a.com is in use while GetEvictionOrigin is running.
+ GetEvictionOrigin(kTemp);
+ NotifyOriginInUse(GURL("https://a.com/"));
+ base::RunLoop().RunUntilIdle();
+ // Post-filtering must have excluded the returned origin, so we will
+ // see empty result here.
+ EXPECT_TRUE(eviction_origin().is_empty());
+
+ // Notify access for http://c.com while GetEvictionOrigin is running.
+ GetEvictionOrigin(kTemp);
+ NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ // Post-filtering must have excluded the returned origin, so we will
+ // see empty result here.
+ EXPECT_TRUE(eviction_origin().is_empty());
+
+ NotifyOriginNoLongerInUse(GURL("http://a.com/"));
+ NotifyOriginNoLongerInUse(GURL("https://a.com/"));
+ GetEvictionOrigin(kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ("http://a.com/", eviction_origin().spec());
+}
+
+TEST_F(QuotaManagerTest, GetOriginsModifiedSince) {
+ static const MockOriginData kData[] = {
+ { "http://a.com/", kTemp, 0 },
+ { "http://a.com:1/", kTemp, 0 },
+ { "https://a.com/", kTemp, 0 },
+ { "http://b.com/", kPerm, 0 }, // persistent
+ { "http://c.com/", kTemp, 0 },
+ };
+ MockStorageClient* client = CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem);
+ RegisterClient(client);
+
+ GetOriginsModifiedSince(kTemp, base::Time());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(modified_origins().empty());
+ EXPECT_EQ(modified_origins_type(), kTemp);
+
+ base::Time time1 = client->IncrementMockTime();
+ client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10);
+ client->ModifyOriginAndNotify(GURL("http://a.com:1/"), kTemp, 10);
+ client->ModifyOriginAndNotify(GURL("http://b.com/"), kPerm, 10);
+ base::Time time2 = client->IncrementMockTime();
+ client->ModifyOriginAndNotify(GURL("https://a.com/"), kTemp, 10);
+ client->ModifyOriginAndNotify(GURL("http://c.com/"), kTemp, 10);
+ base::Time time3 = client->IncrementMockTime();
+
+ GetOriginsModifiedSince(kTemp, time1);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(4U, modified_origins().size());
+ EXPECT_EQ(modified_origins_type(), kTemp);
+ for (size_t i = 0; i < arraysize(kData); ++i) {
+ if (kData[i].type == kTemp)
+ EXPECT_EQ(1U, modified_origins().count(GURL(kData[i].origin)));
+ }
+
+ GetOriginsModifiedSince(kTemp, time2);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2U, modified_origins().size());
+
+ GetOriginsModifiedSince(kTemp, time3);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(modified_origins().empty());
+ EXPECT_EQ(modified_origins_type(), kTemp);
+
+ client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10);
+
+ GetOriginsModifiedSince(kTemp, time3);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1U, modified_origins().size());
+ EXPECT_EQ(1U, modified_origins().count(GURL("http://a.com/")));
+ EXPECT_EQ(modified_origins_type(), kTemp);
+}
+
+TEST_F(QuotaManagerTest, DumpQuotaTable) {
+ SetPersistentHostQuota("example1.com", 1);
+ SetPersistentHostQuota("example2.com", 20);
+ SetPersistentHostQuota("example3.com", 300);
+ base::RunLoop().RunUntilIdle();
+
+ DumpQuotaTable();
+ base::RunLoop().RunUntilIdle();
+
+ const QuotaTableEntry kEntries[] = {
+ QuotaTableEntry("example1.com", kPerm, 1),
+ QuotaTableEntry("example2.com", kPerm, 20),
+ QuotaTableEntry("example3.com", kPerm, 300),
+ };
+ std::set<QuotaTableEntry> entries(kEntries, kEntries + arraysize(kEntries));
+
+ typedef QuotaTableEntries::const_iterator iterator;
+ for (iterator itr(quota_entries().begin()), end(quota_entries().end());
+ itr != end; ++itr) {
+ SCOPED_TRACE(testing::Message()
+ << "host = " << itr->host << ", "
+ << "quota = " << itr->quota);
+ EXPECT_EQ(1u, entries.erase(*itr));
+ }
+ EXPECT_TRUE(entries.empty());
+}
+
+TEST_F(QuotaManagerTest, DumpOriginInfoTable) {
+ using std::make_pair;
+
+ quota_manager()->NotifyStorageAccessed(
+ QuotaClient::kUnknown,
+ GURL("http://example.com/"),
+ kTemp);
+ quota_manager()->NotifyStorageAccessed(
+ QuotaClient::kUnknown,
+ GURL("http://example.com/"),
+ kPerm);
+ quota_manager()->NotifyStorageAccessed(
+ QuotaClient::kUnknown,
+ GURL("http://example.com/"),
+ kPerm);
+ base::RunLoop().RunUntilIdle();
+
+ DumpOriginInfoTable();
+ base::RunLoop().RunUntilIdle();
+
+ typedef std::pair<GURL, StorageType> TypedOrigin;
+ typedef std::pair<TypedOrigin, int> Entry;
+ const Entry kEntries[] = {
+ make_pair(make_pair(GURL("http://example.com/"), kTemp), 1),
+ make_pair(make_pair(GURL("http://example.com/"), kPerm), 2),
+ };
+ std::set<Entry> entries(kEntries, kEntries + arraysize(kEntries));
+
+ typedef OriginInfoTableEntries::const_iterator iterator;
+ for (iterator itr(origin_info_entries().begin()),
+ end(origin_info_entries().end());
+ itr != end; ++itr) {
+ SCOPED_TRACE(testing::Message()
+ << "host = " << itr->origin << ", "
+ << "type = " << itr->type << ", "
+ << "used_count = " << itr->used_count);
+ EXPECT_EQ(1u, entries.erase(
+ make_pair(make_pair(itr->origin, itr->type),
+ itr->used_count)));
+ }
+ EXPECT_TRUE(entries.empty());
+}
+
+TEST_F(QuotaManagerTest, QuotaForEmptyHost) {
+ GetPersistentHostQuota(std::string());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, quota());
+
+ SetPersistentHostQuota(std::string(), 10);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaErrorNotSupported, status());
+}
+
+TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleOrigin) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com/", kTemp, 2 },
+ };
+ static const MockOriginData kData3[] = {
+ { "http://foo.com/", kTemp, 4 },
+ };
+ static const MockOriginData kData4[] = {
+ { "http://foo.com/", kTemp, 8 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kAppcache);
+ MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
+ QuotaClient::kDatabase);
+ MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
+ QuotaClient::kIndexedDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+ RegisterClient(client3);
+ RegisterClient(client4);
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kFileSystem);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 1, usage());
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kAppcache);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp,
+ QuotaClient::kIndexedDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
+TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleHost) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com:1111/", kTemp, 1 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com:2222/", kTemp, 2 },
+ };
+ static const MockOriginData kData3[] = {
+ { "http://foo.com:3333/", kTemp, 4 },
+ };
+ static const MockOriginData kData4[] = {
+ { "http://foo.com:4444/", kTemp, 8 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kAppcache);
+ MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
+ QuotaClient::kDatabase);
+ MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
+ QuotaClient::kIndexedDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+ RegisterClient(client3);
+ RegisterClient(client4);
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ DeleteHostData("foo.com", kTemp, QuotaClient::kFileSystem);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 1, usage());
+
+ DeleteHostData("foo.com", kTemp, QuotaClient::kAppcache);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
+
+ DeleteHostData("foo.com", kTemp, QuotaClient::kDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());
+
+ DeleteHostData("foo.com", kTemp, QuotaClient::kIndexedDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
+TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleOrigin) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com/", kTemp, 1 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com/", kTemp, 2 },
+ };
+ static const MockOriginData kData3[] = {
+ { "http://foo.com/", kTemp, 4 },
+ };
+ static const MockOriginData kData4[] = {
+ { "http://foo.com/", kTemp, 8 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kAppcache);
+ MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
+ QuotaClient::kDatabase);
+ MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
+ QuotaClient::kIndexedDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+ RegisterClient(client3);
+ RegisterClient(client4);
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp,
+ QuotaClient::kFileSystem | QuotaClient::kDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage());
+
+ DeleteOriginData(GURL("http://foo.com/"), kTemp,
+ QuotaClient::kAppcache | QuotaClient::kIndexedDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
+TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleHost) {
+ static const MockOriginData kData1[] = {
+ { "http://foo.com:1111/", kTemp, 1 },
+ };
+ static const MockOriginData kData2[] = {
+ { "http://foo.com:2222/", kTemp, 2 },
+ };
+ static const MockOriginData kData3[] = {
+ { "http://foo.com:3333/", kTemp, 4 },
+ };
+ static const MockOriginData kData4[] = {
+ { "http://foo.com:4444/", kTemp, 8 },
+ };
+ MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
+ QuotaClient::kFileSystem);
+ MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
+ QuotaClient::kAppcache);
+ MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
+ QuotaClient::kDatabase);
+ MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
+ QuotaClient::kIndexedDatabase);
+ RegisterClient(client1);
+ RegisterClient(client2);
+ RegisterClient(client3);
+ RegisterClient(client4);
+
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ const int64_t predelete_foo_tmp = usage();
+
+ DeleteHostData("foo.com", kTemp,
+ QuotaClient::kFileSystem | QuotaClient::kAppcache);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
+
+ DeleteHostData("foo.com", kTemp,
+ QuotaClient::kDatabase | QuotaClient::kIndexedDatabase);
+ base::RunLoop().RunUntilIdle();
+ GetHostUsage("foo.com", kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
+TEST_F(QuotaManagerTest, GetUsageAndQuota_Incognito) {
+ ResetQuotaManager(true);
+
+ static const MockOriginData kData[] = {
+ { "http://foo.com/", kTemp, 10 },
+ { "http://foo.com/", kPerm, 80 },
+ };
+ RegisterClient(CreateClient(kData, arraysize(kData),
+ QuotaClient::kFileSystem));
+
+ // Query global usage to warmup the usage tracker caching.
+ GetGlobalUsage(kTemp);
+ GetGlobalUsage(kPerm);
+ base::RunLoop().RunUntilIdle();
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(80, usage());
+ EXPECT_EQ(0, quota());
+
+ const int kPoolSize = 1000;
+ const int kPerHostQuota = kPoolSize / 5;
+ SetQuotaSettings(kPoolSize, kPerHostQuota, INT64_C(0));
+
+ GetStorageCapacity();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kPoolSize, total_space());
+ EXPECT_EQ(kPoolSize - 80 - 10, available_space());
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_LE(kPerHostQuota, quota());
+
+ mock_special_storage_policy()->AddUnlimited(GURL("http://foo.com/"));
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(80, usage());
+ EXPECT_EQ(available_space() + usage(), quota());
+
+ GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_EQ(available_space() + usage(), quota());
+}
+
+TEST_F(QuotaManagerTest, GetUsageAndQuota_SessionOnly) {
+ const GURL kEpheremalOrigin("http://ephemeral/");
+ mock_special_storage_policy()->AddSessionOnly(kEpheremalOrigin);
+
+ GetUsageAndQuotaForWebApps(kEpheremalOrigin, kTemp);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(quota_manager()->settings().session_only_per_host_quota, quota());
+
+ GetUsageAndQuotaForWebApps(kEpheremalOrigin, kPerm);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, quota());
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc b/chromium/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
new file mode 100644
index 00000000000..f8a1cc8d9ef
--- /dev/null
+++ b/chromium/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
@@ -0,0 +1,402 @@
+// 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 <stdint.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_temporary_storage_evictor.h"
+#include "storage/browser/test/mock_storage_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using storage::QuotaTemporaryStorageEvictor;
+using storage::StorageType;
+
+namespace content {
+
+class QuotaTemporaryStorageEvictorTest;
+
+namespace {
+
+class MockQuotaEvictionHandler : public storage::QuotaEvictionHandler {
+ public:
+ explicit MockQuotaEvictionHandler(QuotaTemporaryStorageEvictorTest* test)
+ : available_space_(0),
+ error_on_evict_origin_data_(false),
+ error_on_get_usage_and_quota_(false) {}
+
+ void EvictOriginData(const GURL& origin,
+ StorageType type,
+ const storage::StatusCallback& callback) override {
+ if (error_on_evict_origin_data_) {
+ callback.Run(storage::kQuotaErrorInvalidModification);
+ return;
+ }
+ int64_t origin_usage = EnsureOriginRemoved(origin);
+ if (origin_usage >= 0)
+ available_space_ += origin_usage;
+ callback.Run(storage::kQuotaStatusOk);
+ }
+
+ void GetEvictionRoundInfo(
+ const EvictionRoundInfoCallback& callback) override {
+ if (error_on_get_usage_and_quota_) {
+ callback.Run(storage::kQuotaErrorAbort, storage::QuotaSettings(), 0, 0,
+ 0, false);
+ return;
+ }
+ if (!task_for_get_usage_and_quota_.is_null())
+ task_for_get_usage_and_quota_.Run();
+ callback.Run(storage::kQuotaStatusOk, settings_, available_space_,
+ available_space_ * 2, GetUsage(), true);
+ }
+
+ void GetEvictionOrigin(StorageType type,
+ const std::set<GURL>& exceptions,
+ int64_t global_quota,
+ const storage::GetOriginCallback& callback) override {
+ if (origin_order_.empty())
+ callback.Run(GURL());
+ else
+ callback.Run(GURL(origin_order_.front()));
+ }
+
+ int64_t GetUsage() const {
+ int64_t total_usage = 0;
+ for (std::map<GURL, int64_t>::const_iterator p = origins_.begin();
+ p != origins_.end(); ++p)
+ total_usage += p->second;
+ return total_usage;
+ }
+
+ const storage::QuotaSettings& settings() const { return settings_; }
+ void SetPoolSize(int64_t pool_size) {
+ settings_.pool_size = pool_size;
+ settings_.per_host_quota = pool_size / 5;
+ settings_.should_remain_available = pool_size / 5;
+ settings_.must_remain_available = pool_size / 100;
+ settings_.refresh_interval = base::TimeDelta::Max();
+ }
+ void set_available_space(int64_t available_space) {
+ available_space_ = available_space;
+ }
+ void set_task_for_get_usage_and_quota(const base::Closure& task) {
+ task_for_get_usage_and_quota_= task;
+ }
+ void set_error_on_evict_origin_data(bool error_on_evict_origin_data) {
+ error_on_evict_origin_data_ = error_on_evict_origin_data;
+ }
+ void set_error_on_get_usage_and_quota(bool error_on_get_usage_and_quota) {
+ error_on_get_usage_and_quota_ = error_on_get_usage_and_quota;
+ }
+
+ // Simulates an access to |origin|. It reorders the internal LRU list.
+ // It internally uses AddOrigin().
+ void AccessOrigin(const GURL& origin) {
+ std::map<GURL, int64_t>::iterator found = origins_.find(origin);
+ EXPECT_TRUE(origins_.end() != found);
+ AddOrigin(origin, found->second);
+ }
+
+ // Simulates adding or overwriting the |origin| to the internal origin set
+ // with the |usage|. It also adds or moves the |origin| to the end of the
+ // LRU list.
+ void AddOrigin(const GURL& origin, int64_t usage) {
+ EnsureOriginRemoved(origin);
+ origin_order_.push_back(origin);
+ origins_[origin] = usage;
+ }
+
+ private:
+ int64_t EnsureOriginRemoved(const GURL& origin) {
+ int64_t origin_usage;
+ if (origins_.find(origin) == origins_.end())
+ return -1;
+ else
+ origin_usage = origins_[origin];
+
+ origins_.erase(origin);
+ origin_order_.remove(origin);
+ return origin_usage;
+ }
+
+ storage::QuotaSettings settings_;
+ int64_t available_space_;
+ std::list<GURL> origin_order_;
+ std::map<GURL, int64_t> origins_;
+ bool error_on_evict_origin_data_;
+ bool error_on_get_usage_and_quota_;
+
+ base::Closure task_for_get_usage_and_quota_;
+};
+
+} // namespace
+
+class QuotaTemporaryStorageEvictorTest : public testing::Test {
+ public:
+ QuotaTemporaryStorageEvictorTest()
+ : num_get_usage_and_quota_for_eviction_(0),
+ weak_factory_(this) {}
+
+ void SetUp() override {
+ quota_eviction_handler_.reset(new MockQuotaEvictionHandler(this));
+
+ // Run multiple evictions in a single RunUntilIdle() when interval_ms == 0
+ temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
+ quota_eviction_handler_.get(), 0));
+ }
+
+ void TearDown() override {
+ temporary_storage_evictor_.reset();
+ quota_eviction_handler_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void TaskForRepeatedEvictionTest(
+ const std::pair<GURL, int64_t>& origin_to_be_added,
+ const GURL& origin_to_be_accessed,
+ int expected_usage_after_first,
+ int expected_usage_after_second) {
+ EXPECT_GE(4, num_get_usage_and_quota_for_eviction_);
+ switch (num_get_usage_and_quota_for_eviction_) {
+ case 2:
+ EXPECT_EQ(expected_usage_after_first,
+ quota_eviction_handler()->GetUsage());
+ if (!origin_to_be_added.first.is_empty())
+ quota_eviction_handler()->AddOrigin(origin_to_be_added.first,
+ origin_to_be_added.second);
+ if (!origin_to_be_accessed.is_empty())
+ quota_eviction_handler()->AccessOrigin(origin_to_be_accessed);
+ break;
+ case 3:
+ EXPECT_EQ(expected_usage_after_second,
+ quota_eviction_handler()->GetUsage());
+ temporary_storage_evictor()->timer_disabled_for_testing_ = true;
+ break;
+ }
+ ++num_get_usage_and_quota_for_eviction_;
+ }
+
+ protected:
+ MockQuotaEvictionHandler* quota_eviction_handler() const {
+ return static_cast<MockQuotaEvictionHandler*>(
+ quota_eviction_handler_.get());
+ }
+
+ QuotaTemporaryStorageEvictor* temporary_storage_evictor() const {
+ return temporary_storage_evictor_.get();
+ }
+
+ const QuotaTemporaryStorageEvictor::Statistics& statistics() const {
+ return temporary_storage_evictor()->statistics_;
+ }
+
+ void disable_timer_for_testing() const {
+ temporary_storage_evictor_->timer_disabled_for_testing_ = true;
+ }
+
+ int num_get_usage_and_quota_for_eviction() const {
+ return num_get_usage_and_quota_for_eviction_;
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ std::unique_ptr<MockQuotaEvictionHandler> quota_eviction_handler_;
+ std::unique_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_;
+ int num_get_usage_and_quota_for_eviction_;
+ base::WeakPtrFactory<QuotaTemporaryStorageEvictorTest> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictorTest);
+};
+
+TEST_F(QuotaTemporaryStorageEvictorTest, SimpleEvictionTest) {
+ quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 3000);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 200);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 500);
+ quota_eviction_handler()->SetPoolSize(4000);
+ quota_eviction_handler()->set_available_space(1000000000);
+ EXPECT_EQ(3000 + 200 + 500, quota_eviction_handler()->GetUsage());
+ disable_timer_for_testing();
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(200 + 500, quota_eviction_handler()->GetUsage());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(1, statistics().num_evicted_origins);
+ EXPECT_EQ(1, statistics().num_eviction_rounds);
+ EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, MultipleEvictionTest) {
+ quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 20);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 2900);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 400);
+ quota_eviction_handler()->SetPoolSize(4000);
+ quota_eviction_handler()->set_available_space(1000000000);
+ EXPECT_EQ(20 + 2900 + 450 + 400, quota_eviction_handler()->GetUsage());
+ disable_timer_for_testing();
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(450 + 400, quota_eviction_handler()->GetUsage());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(2, statistics().num_evicted_origins);
+ EXPECT_EQ(1, statistics().num_eviction_rounds);
+ EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionTest) {
+ const int64_t a_size = 400;
+ const int64_t b_size = 150;
+ const int64_t c_size = 120;
+ const int64_t d_size = 292;
+ const int64_t initial_total_size = a_size + b_size + c_size + d_size;
+ const int64_t e_size = 275;
+
+ quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+ quota_eviction_handler()->SetPoolSize(1000);
+ quota_eviction_handler()->set_available_space(1000000000);
+ quota_eviction_handler()->set_task_for_get_usage_and_quota(
+ base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
+ weak_factory_.GetWeakPtr(),
+ std::make_pair(GURL("http://www.e.com"), e_size), GURL(),
+ initial_total_size - d_size,
+ initial_total_size - d_size + e_size - c_size));
+ EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(initial_total_size - d_size + e_size - c_size - b_size,
+ quota_eviction_handler()->GetUsage());
+ EXPECT_EQ(5, num_get_usage_and_quota_for_eviction());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(3, statistics().num_evicted_origins);
+ EXPECT_EQ(2, statistics().num_eviction_rounds);
+ EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest) {
+ const int64_t a_size = 400;
+ const int64_t b_size = 150;
+ const int64_t c_size = 120;
+ const int64_t d_size = 292;
+ const int64_t initial_total_size = a_size + b_size + c_size + d_size;
+
+ quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+ quota_eviction_handler()->SetPoolSize(1000);
+ quota_eviction_handler()->set_available_space(1000000000);
+ quota_eviction_handler()->set_task_for_get_usage_and_quota(
+ base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
+ weak_factory_.GetWeakPtr(), std::make_pair(GURL(), 0), GURL(),
+ initial_total_size - d_size, initial_total_size - d_size));
+ EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
+ // disable_timer_for_testing();
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(initial_total_size - d_size, quota_eviction_handler()->GetUsage());
+ EXPECT_EQ(4, num_get_usage_and_quota_for_eviction());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(1, statistics().num_evicted_origins);
+ EXPECT_EQ(3, statistics().num_eviction_rounds);
+ EXPECT_EQ(2, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionWithAccessOriginTest) {
+ const int64_t a_size = 400;
+ const int64_t b_size = 150;
+ const int64_t c_size = 120;
+ const int64_t d_size = 292;
+ const int64_t initial_total_size = a_size + b_size + c_size + d_size;
+ const int64_t e_size = 275;
+
+ quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+ quota_eviction_handler()->SetPoolSize(1000);
+ quota_eviction_handler()->set_available_space(1000000000);
+ quota_eviction_handler()->set_task_for_get_usage_and_quota(
+ base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
+ weak_factory_.GetWeakPtr(),
+ std::make_pair(GURL("http://www.e.com"), e_size),
+ GURL("http://www.c.com"),
+ initial_total_size - d_size,
+ initial_total_size - d_size + e_size - b_size));
+ EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(initial_total_size - d_size + e_size - b_size - a_size,
+ quota_eviction_handler()->GetUsage());
+ EXPECT_EQ(5, num_get_usage_and_quota_for_eviction());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(3, statistics().num_evicted_origins);
+ EXPECT_EQ(2, statistics().num_eviction_rounds);
+ EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceNonEvictionTest) {
+ // If we're using so little that evicting all of it wouldn't
+ // do enough to alleviate a diskspace shortage, we don't evict.
+ quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 10);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 20);
+ quota_eviction_handler()->SetPoolSize(10000);
+ quota_eviction_handler()->set_available_space(
+ quota_eviction_handler()->settings().should_remain_available - 350);
+ EXPECT_EQ(10 + 20, quota_eviction_handler()->GetUsage());
+ disable_timer_for_testing();
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(10 + 20, quota_eviction_handler()->GetUsage());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(0, statistics().num_evicted_origins);
+ EXPECT_EQ(1, statistics().num_eviction_rounds);
+ EXPECT_EQ(1, statistics().num_skipped_eviction_rounds);
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) {
+ quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 294);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 120);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 150);
+ quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 300);
+ quota_eviction_handler()->SetPoolSize(10000);
+ quota_eviction_handler()->set_available_space(
+ quota_eviction_handler()->settings().should_remain_available - 350);
+ EXPECT_EQ(294 + 120 + 150 + 300, quota_eviction_handler()->GetUsage());
+ disable_timer_for_testing();
+ temporary_storage_evictor()->Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(150 + 300, quota_eviction_handler()->GetUsage());
+
+ EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
+ EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+ EXPECT_EQ(2, statistics().num_evicted_origins);
+ EXPECT_EQ(1, statistics().num_eviction_rounds);
+ EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); // FIXME?
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/quota/storage_monitor_unittest.cc b/chromium/storage/browser/quota/storage_monitor_unittest.cc
new file mode 100644
index 00000000000..f3a20749c4e
--- /dev/null
+++ b/chromium/storage/browser/quota/storage_monitor_unittest.cc
@@ -0,0 +1,706 @@
+// 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 <stdint.h>
+
+#include <vector>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/url_util.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/quota/storage_monitor.h"
+#include "storage/browser/quota/storage_observer.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "storage/browser/test/mock_storage_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using storage::HostStorageObservers;
+using storage::kQuotaErrorNotSupported;
+using storage::kQuotaStatusOk;
+using storage::kStorageTypePersistent;
+using storage::kStorageTypeTemporary;
+using storage::QuotaClient;
+using storage::QuotaManager;
+using storage::QuotaStatusCode;
+using storage::SpecialStoragePolicy;
+using storage::StorageMonitor;
+using storage::StorageObserver;
+using storage::StorageObserverList;
+using storage::StorageType;
+using storage::StorageTypeObservers;
+
+namespace content {
+
+namespace {
+
+const char kDefaultOrigin[] = "http://www.foo.com/";
+const char kAlternativeOrigin[] = "http://www.bar.com/";
+
+class MockObserver : public StorageObserver {
+ public:
+ const StorageObserver::Event& LastEvent() const {
+ CHECK(!events_.empty());
+ return events_.back();
+ }
+
+ int EventCount() const {
+ return events_.size();
+ }
+
+ // StorageObserver implementation:
+ void OnStorageEvent(const StorageObserver::Event& event) override {
+ events_.push_back(event);
+ }
+
+ private:
+ std::vector<StorageObserver::Event> events_;
+};
+
+// A mock quota manager for overriding GetUsageAndQuotaForWebApps().
+class UsageMockQuotaManager : public QuotaManager {
+ public:
+ UsageMockQuotaManager(SpecialStoragePolicy* special_storage_policy)
+ : QuotaManager(false,
+ base::FilePath(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ special_storage_policy,
+ storage::GetQuotaSettingsFunc()),
+ callback_usage_(0),
+ callback_quota_(0),
+ callback_status_(kQuotaStatusOk),
+ initialized_(false) {}
+
+ void SetCallbackParams(int64_t usage, int64_t quota, QuotaStatusCode status) {
+ initialized_ = true;
+ callback_quota_ = quota;
+ callback_usage_ = usage;
+ callback_status_ = status;
+ }
+
+ void InvokeCallback() {
+ delayed_callback_.Run(callback_status_, callback_usage_, callback_quota_);
+ }
+
+ void GetUsageAndQuotaForWebApps(
+ const GURL& origin,
+ StorageType type,
+ const UsageAndQuotaCallback& callback) override {
+ if (initialized_)
+ callback.Run(callback_status_, callback_usage_, callback_quota_);
+ else
+ delayed_callback_ = callback;
+ }
+
+ protected:
+ ~UsageMockQuotaManager() override {}
+
+ private:
+ int64_t callback_usage_;
+ int64_t callback_quota_;
+ QuotaStatusCode callback_status_;
+ bool initialized_;
+ UsageAndQuotaCallback delayed_callback_;
+};
+
+} // namespace
+
+class StorageMonitorTestBase : public testing::Test {
+ protected:
+ void DispatchPendingEvents(StorageObserverList& observer_list) {
+ observer_list.DispatchPendingEvent();
+ }
+
+ const StorageObserver::Event* GetPendingEvent(
+ const StorageObserverList& observer_list) {
+ return observer_list.notification_timer_.IsRunning()
+ ? &observer_list.pending_event_ : NULL;
+ }
+
+ const StorageObserver::Event* GetPendingEvent(
+ const HostStorageObservers& host_observers) {
+ return GetPendingEvent(host_observers.observers_);
+ }
+
+ int GetRequiredUpdatesCount(const StorageObserverList& observer_list) {
+ int count = 0;
+ for (StorageObserverList::StorageObserverStateMap::const_iterator it =
+ observer_list.observers_.begin();
+ it != observer_list.observers_.end(); ++it) {
+ if (it->second.requires_update)
+ ++count;
+ }
+
+ return count;
+ }
+
+ int GetRequiredUpdatesCount(const HostStorageObservers& host_observers) {
+ return GetRequiredUpdatesCount(host_observers.observers_);
+ }
+
+ void SetLastNotificationTime(StorageObserverList& observer_list,
+ StorageObserver* observer) {
+ ASSERT_TRUE(observer_list.observers_.find(observer) !=
+ observer_list.observers_.end());
+
+ StorageObserverList::ObserverState& state =
+ observer_list.observers_[observer];
+ state.last_notification_time = base::TimeTicks::Now() - state.rate;
+ }
+
+ void SetLastNotificationTime(HostStorageObservers& host_observers,
+ StorageObserver* observer) {
+ SetLastNotificationTime(host_observers.observers_, observer);
+ }
+
+ int GetObserverCount(const HostStorageObservers& host_observers) {
+ return host_observers.observers_.ObserverCount();
+ }
+};
+
+class StorageTestWithManagerBase : public StorageMonitorTestBase {
+ public:
+ void SetUp() override {
+ storage_policy_ = new MockSpecialStoragePolicy();
+ quota_manager_ = new UsageMockQuotaManager(storage_policy_.get());
+ }
+
+ void TearDown() override {
+ // This ensures the quota manager is destroyed correctly.
+ quota_manager_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
+ scoped_refptr<UsageMockQuotaManager> quota_manager_;
+};
+
+// Tests for StorageObserverList:
+
+typedef StorageMonitorTestBase StorageObserverListTest;
+
+// Test dispatching events to one observer.
+TEST_F(StorageObserverListTest, DispatchEventToSingleObserver) {
+ // A message loop is required as StorageObserverList may schedule jobs.
+ base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT);
+
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer;
+ StorageObserverList observer_list;
+ observer_list.AddObserver(&mock_observer, params);
+
+ StorageObserver::Event event;
+ event.filter = params.filter;
+
+ // Verify that the first event is dispatched immediately.
+ event.quota = 1;
+ event.usage = 1;
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(1, mock_observer.EventCount());
+ EXPECT_EQ(event, mock_observer.LastEvent());
+ EXPECT_EQ(NULL, GetPendingEvent(observer_list));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
+
+ // Verify that the next event is pending.
+ event.quota = 2;
+ event.usage = 2;
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(1, mock_observer.EventCount());
+ ASSERT_TRUE(GetPendingEvent(observer_list));
+ EXPECT_EQ(event, *GetPendingEvent(observer_list));
+ EXPECT_EQ(1, GetRequiredUpdatesCount(observer_list));
+
+ // Fake the last notification time so that an event will be dispatched.
+ SetLastNotificationTime(observer_list, &mock_observer);
+ event.quota = 3;
+ event.usage = 3;
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(2, mock_observer.EventCount());
+ EXPECT_EQ(event, mock_observer.LastEvent());
+ EXPECT_EQ(NULL, GetPendingEvent(observer_list));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
+
+ // Remove the observer.
+ event.quota = 4;
+ event.usage = 4;
+ observer_list.RemoveObserver(&mock_observer);
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(2, mock_observer.EventCount());
+ EXPECT_EQ(NULL, GetPendingEvent(observer_list));
+}
+
+// Test dispatching events to multiple observers.
+TEST_F(StorageObserverListTest, DispatchEventToMultipleObservers) {
+ // A message loop is required as StorageObserverList may schedule jobs.
+ base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT);
+
+ MockObserver mock_observer1;
+ MockObserver mock_observer2;
+ StorageObserverList observer_list;
+ StorageObserver::Filter filter(kStorageTypePersistent,
+ GURL(kDefaultOrigin));
+ observer_list.AddObserver(
+ &mock_observer1,
+ StorageObserver::MonitorParams(
+ filter, base::TimeDelta::FromHours(1), false));
+ observer_list.AddObserver(
+ &mock_observer2,
+ StorageObserver::MonitorParams(
+ filter, base::TimeDelta::FromHours(2), false));
+
+ StorageObserver::Event event;
+ event.filter = filter;
+
+ // Verify that the first event is dispatched immediately.
+ event.quota = 1;
+ event.usage = 1;
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(1, mock_observer1.EventCount());
+ EXPECT_EQ(1, mock_observer2.EventCount());
+ EXPECT_EQ(event, mock_observer1.LastEvent());
+ EXPECT_EQ(event, mock_observer2.LastEvent());
+ EXPECT_EQ(NULL, GetPendingEvent(observer_list));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
+
+ // Fake the last notification time so that observer1 will receive the next
+ // event, but it will be pending for observer2.
+ SetLastNotificationTime(observer_list, &mock_observer1);
+ event.quota = 2;
+ event.usage = 2;
+ observer_list.OnStorageChange(event);
+ EXPECT_EQ(2, mock_observer1.EventCount());
+ EXPECT_EQ(1, mock_observer2.EventCount());
+ EXPECT_EQ(event, mock_observer1.LastEvent());
+ ASSERT_TRUE(GetPendingEvent(observer_list));
+ EXPECT_EQ(event, *GetPendingEvent(observer_list));
+ EXPECT_EQ(1, GetRequiredUpdatesCount(observer_list));
+
+ // Now dispatch the pending event to observer2.
+ SetLastNotificationTime(observer_list, &mock_observer2);
+ DispatchPendingEvents(observer_list);
+ EXPECT_EQ(2, mock_observer1.EventCount());
+ EXPECT_EQ(2, mock_observer2.EventCount());
+ EXPECT_EQ(event, mock_observer1.LastEvent());
+ EXPECT_EQ(event, mock_observer2.LastEvent());
+ EXPECT_EQ(NULL, GetPendingEvent(observer_list));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
+}
+
+// Ensure that the |origin| field in events match the origin specified by the
+// observer on registration.
+TEST_F(StorageObserverListTest, ReplaceEventOrigin) {
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer;
+ StorageObserverList observer_list;
+ observer_list.AddObserver(&mock_observer, params);
+
+ StorageObserver::Event dispatched_event;
+ dispatched_event.filter = params.filter;
+ dispatched_event.filter.origin = GURL("https://www.foo.com/bar");
+ observer_list.OnStorageChange(dispatched_event);
+
+ EXPECT_EQ(params.filter.origin, mock_observer.LastEvent().filter.origin);
+}
+
+// Tests for HostStorageObservers:
+
+typedef StorageTestWithManagerBase HostStorageObserversTest;
+
+// Verify that HostStorageObservers is initialized after the first usage change.
+TEST_F(HostStorageObserversTest, InitializeOnUsageChange) {
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ const int64_t kUsage = 324554;
+ const int64_t kQuota = 234354354;
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+
+ MockObserver mock_observer;
+ HostStorageObservers host_observers(quota_manager_.get());
+ host_observers.AddObserver(&mock_observer, params);
+
+ // Verify that HostStorageObservers dispatches the first event correctly.
+ StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
+ host_observers.NotifyUsageChange(params.filter, 87324);
+ EXPECT_EQ(1, mock_observer.EventCount());
+ EXPECT_EQ(expected_event, mock_observer.LastEvent());
+ EXPECT_TRUE(host_observers.is_initialized());
+
+ // Verify that HostStorageObservers handles subsequent usage changes
+ // correctly.
+ const int64_t kDelta = 2345;
+ expected_event.usage += kDelta;
+ SetLastNotificationTime(host_observers, &mock_observer);
+ host_observers.NotifyUsageChange(params.filter, kDelta);
+ EXPECT_EQ(2, mock_observer.EventCount());
+ EXPECT_EQ(expected_event, mock_observer.LastEvent());
+}
+
+// Verify that HostStorageObservers is initialized after the adding the first
+// observer that elected to receive the initial state.
+TEST_F(HostStorageObserversTest, InitializeOnObserver) {
+ const int64_t kUsage = 74387;
+ const int64_t kQuota = 92834743;
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+ HostStorageObservers host_observers(quota_manager_.get());
+
+ // |host_observers| should not be initialized after the first observer is
+ // added because it did not elect to receive the initial state.
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer1;
+ host_observers.AddObserver(&mock_observer1, params);
+ EXPECT_FALSE(host_observers.is_initialized());
+ EXPECT_EQ(0, mock_observer1.EventCount());
+
+ // |host_observers| should be initialized after the second observer is
+ // added.
+ MockObserver mock_observer2;
+ params.dispatch_initial_state = true;
+ host_observers.AddObserver(&mock_observer2, params);
+ StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
+ EXPECT_EQ(0, mock_observer1.EventCount());
+ EXPECT_EQ(1, mock_observer2.EventCount());
+ EXPECT_EQ(expected_event, mock_observer2.LastEvent());
+ EXPECT_TRUE(host_observers.is_initialized());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+
+ // Verify that both observers will receive events after a usage change.
+ const int64_t kDelta = 2345;
+ expected_event.usage += kDelta;
+ SetLastNotificationTime(host_observers, &mock_observer2);
+ host_observers.NotifyUsageChange(params.filter, kDelta);
+ EXPECT_EQ(1, mock_observer1.EventCount());
+ EXPECT_EQ(2, mock_observer2.EventCount());
+ EXPECT_EQ(expected_event, mock_observer1.LastEvent());
+ EXPECT_EQ(expected_event, mock_observer2.LastEvent());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+
+ // Verify that the addition of a third observer only causes an event to be
+ // dispatched to the new observer.
+ MockObserver mock_observer3;
+ params.dispatch_initial_state = true;
+ host_observers.AddObserver(&mock_observer3, params);
+ EXPECT_EQ(1, mock_observer1.EventCount());
+ EXPECT_EQ(2, mock_observer2.EventCount());
+ EXPECT_EQ(1, mock_observer3.EventCount());
+ EXPECT_EQ(expected_event, mock_observer3.LastEvent());
+}
+
+// Verify that negative usage and quota is changed to zero.
+TEST_F(HostStorageObserversTest, NegativeUsageAndQuota) {
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ const int64_t kUsage = -324554;
+ const int64_t kQuota = -234354354;
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+
+ MockObserver mock_observer;
+ HostStorageObservers host_observers(quota_manager_.get());
+ host_observers.AddObserver(&mock_observer, params);
+
+ StorageObserver::Event expected_event(params.filter, 0, 0);
+ host_observers.NotifyUsageChange(params.filter, -87324);
+ EXPECT_EQ(expected_event, mock_observer.LastEvent());
+}
+
+// Verify that HostStorageObservers can recover from a bad initialization.
+TEST_F(HostStorageObserversTest, RecoverFromBadUsageInit) {
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer;
+ HostStorageObservers host_observers(quota_manager_.get());
+ host_observers.AddObserver(&mock_observer, params);
+
+ // Set up the quota manager to return an error status.
+ const int64_t kUsage = 6656;
+ const int64_t kQuota = 99585556;
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaErrorNotSupported);
+
+ // Verify that |host_observers| is not initialized and an event has not been
+ // dispatched.
+ host_observers.NotifyUsageChange(params.filter, 9438);
+ EXPECT_EQ(0, mock_observer.EventCount());
+ EXPECT_FALSE(host_observers.is_initialized());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+
+ // Now ensure that quota manager returns a good status.
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+ host_observers.NotifyUsageChange(params.filter, 9048543);
+ StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
+ EXPECT_EQ(1, mock_observer.EventCount());
+ EXPECT_EQ(expected_event, mock_observer.LastEvent());
+ EXPECT_TRUE(host_observers.is_initialized());
+}
+
+// Verify that HostStorageObservers handle initialization of the cached usage
+// and quota correctly.
+TEST_F(HostStorageObserversTest, AsyncInitialization) {
+ StorageObserver::MonitorParams params(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer;
+ HostStorageObservers host_observers(quota_manager_.get());
+ host_observers.AddObserver(&mock_observer, params);
+
+ // Trigger initialization. Leave the mock quota manager uninitialized so that
+ // the callback is not invoked.
+ host_observers.NotifyUsageChange(params.filter, 7645);
+ EXPECT_EQ(0, mock_observer.EventCount());
+ EXPECT_FALSE(host_observers.is_initialized());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+
+ // Simulate notifying |host_observers| of a usage change before initialization
+ // is complete.
+ const int64_t kUsage = 6656;
+ const int64_t kQuota = 99585556;
+ const int64_t kDelta = 327643;
+ host_observers.NotifyUsageChange(params.filter, kDelta);
+ EXPECT_EQ(0, mock_observer.EventCount());
+ EXPECT_FALSE(host_observers.is_initialized());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+
+ // Simulate an asynchronous callback from QuotaManager.
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+ quota_manager_->InvokeCallback();
+ StorageObserver::Event expected_event(params.filter, kUsage + kDelta, kQuota);
+ EXPECT_EQ(1, mock_observer.EventCount());
+ EXPECT_EQ(expected_event, mock_observer.LastEvent());
+ EXPECT_TRUE(host_observers.is_initialized());
+ EXPECT_EQ(NULL, GetPendingEvent(host_observers));
+ EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
+}
+
+// Tests for StorageTypeObservers:
+
+typedef StorageTestWithManagerBase StorageTypeObserversTest;
+
+// Test adding and removing observers.
+TEST_F(StorageTypeObserversTest, AddRemoveObservers) {
+ StorageTypeObservers type_observers(quota_manager_.get());
+
+ StorageObserver::MonitorParams params1(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ StorageObserver::MonitorParams params2(kStorageTypePersistent,
+ GURL(kAlternativeOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ std::string host1 = net::GetHostOrSpecFromURL(params1.filter.origin);
+ std::string host2 = net::GetHostOrSpecFromURL(params2.filter.origin);
+
+ MockObserver mock_observer1;
+ MockObserver mock_observer2;
+ MockObserver mock_observer3;
+ type_observers.AddObserver(&mock_observer1, params1);
+ type_observers.AddObserver(&mock_observer2, params1);
+
+ type_observers.AddObserver(&mock_observer1, params2);
+ type_observers.AddObserver(&mock_observer2, params2);
+ type_observers.AddObserver(&mock_observer3, params2);
+
+ // Verify that the observers have been removed correctly.
+ ASSERT_TRUE(type_observers.GetHostObservers(host1));
+ ASSERT_TRUE(type_observers.GetHostObservers(host2));
+ EXPECT_EQ(2, GetObserverCount(*type_observers.GetHostObservers(host1)));
+ EXPECT_EQ(3, GetObserverCount(*type_observers.GetHostObservers(host2)));
+
+ // Remove an observer for a specific filter.
+ type_observers.RemoveObserverForFilter(&mock_observer1, params1.filter);
+ ASSERT_TRUE(type_observers.GetHostObservers(host1));
+ ASSERT_TRUE(type_observers.GetHostObservers(host2));
+ EXPECT_EQ(1, GetObserverCount(*type_observers.GetHostObservers(host1)));
+ EXPECT_EQ(3, GetObserverCount(*type_observers.GetHostObservers(host2)));
+
+ // Remove all instances of an observer.
+ type_observers.RemoveObserver(&mock_observer2);
+ ASSERT_TRUE(type_observers.GetHostObservers(host2));
+ EXPECT_EQ(2, GetObserverCount(*type_observers.GetHostObservers(host2)));
+ // Observers of host1 has been deleted as it is empty.
+ EXPECT_FALSE(type_observers.GetHostObservers(host1));
+}
+
+// Tests for StorageMonitor:
+
+class StorageMonitorTest : public StorageTestWithManagerBase {
+ public:
+ StorageMonitorTest()
+ : storage_monitor_(NULL),
+ params1_(kStorageTypeTemporary,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false),
+ params2_(kStorageTypePersistent,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false) {
+ }
+
+ protected:
+ void SetUp() override {
+ StorageTestWithManagerBase::SetUp();
+
+ storage_monitor_ = quota_manager_->storage_monitor_.get();
+ host_ = net::GetHostOrSpecFromURL(params1_.filter.origin);
+
+ storage_monitor_->AddObserver(&mock_observer1_, params1_);
+ storage_monitor_->AddObserver(&mock_observer2_, params1_);
+
+ storage_monitor_->AddObserver(&mock_observer1_, params2_);
+ storage_monitor_->AddObserver(&mock_observer2_, params2_);
+ storage_monitor_->AddObserver(&mock_observer3_, params2_);
+ }
+
+ int GetObserverCount(StorageType storage_type) {
+ const StorageTypeObservers* type_observers =
+ storage_monitor_->GetStorageTypeObservers(storage_type);
+ return StorageMonitorTestBase::GetObserverCount(
+ *type_observers->GetHostObservers(host_));
+ }
+
+ void CheckObserverCount(int expected_temporary, int expected_persistent) {
+ ASSERT_TRUE(storage_monitor_->GetStorageTypeObservers(
+ kStorageTypeTemporary));
+ ASSERT_TRUE(storage_monitor_->GetStorageTypeObservers(
+ kStorageTypeTemporary)->GetHostObservers(host_));
+ EXPECT_EQ(expected_temporary, GetObserverCount(kStorageTypeTemporary));
+
+ ASSERT_TRUE(storage_monitor_->GetStorageTypeObservers(
+ kStorageTypePersistent));
+ ASSERT_TRUE(storage_monitor_->GetStorageTypeObservers(
+ kStorageTypePersistent)->GetHostObservers(host_));
+ EXPECT_EQ(expected_persistent, GetObserverCount(kStorageTypePersistent));
+ }
+
+ StorageMonitor* storage_monitor_;
+ StorageObserver::MonitorParams params1_;
+ StorageObserver::MonitorParams params2_;
+ MockObserver mock_observer1_;
+ MockObserver mock_observer2_;
+ MockObserver mock_observer3_;
+ std::string host_;
+};
+
+// Test adding storage observers.
+TEST_F(StorageMonitorTest, AddObservers) {
+ // Verify that the observers are added correctly.
+ CheckObserverCount(2, 3);
+}
+
+// Test dispatching events to storage observers.
+TEST_F(StorageMonitorTest, EventDispatch) {
+ // Verify dispatch of events.
+ const int64_t kUsage = 5325;
+ const int64_t kQuota = 903845;
+ quota_manager_->SetCallbackParams(kUsage, kQuota, kQuotaStatusOk);
+ storage_monitor_->NotifyUsageChange(params1_.filter, 9048543);
+
+ StorageObserver::Event expected_event(params1_.filter, kUsage, kQuota);
+ EXPECT_EQ(1, mock_observer1_.EventCount());
+ EXPECT_EQ(1, mock_observer2_.EventCount());
+ EXPECT_EQ(0, mock_observer3_.EventCount());
+ EXPECT_EQ(expected_event, mock_observer1_.LastEvent());
+ EXPECT_EQ(expected_event, mock_observer2_.LastEvent());
+}
+
+// Test removing all instances of an observer.
+TEST_F(StorageMonitorTest, RemoveObserver) {
+ storage_monitor_->RemoveObserver(&mock_observer1_);
+ CheckObserverCount(1, 2);
+}
+
+// Test removing an observer for a specific filter.
+TEST_F(StorageMonitorTest, RemoveObserverForFilter) {
+ storage_monitor_->RemoveObserverForFilter(&mock_observer1_, params2_.filter);
+ CheckObserverCount(2, 2);
+}
+
+// Integration test for QuotaManager and StorageMonitor:
+
+class StorageMonitorIntegrationTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ storage_policy_ = new MockSpecialStoragePolicy();
+ quota_manager_ = new QuotaManager(
+ false, data_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get().get(),
+ base::ThreadTaskRunnerHandle::Get().get(), storage_policy_.get(),
+ storage::GetQuotaSettingsFunc());
+
+ client_ = new MockStorageClient(quota_manager_->proxy(),
+ NULL,
+ QuotaClient::kFileSystem,
+ 0);
+
+ quota_manager_->proxy()->RegisterClient(client_);
+ }
+
+ void TearDown() override {
+ // This ensures the quota manager is destroyed correctly.
+ quota_manager_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir data_dir_;
+ scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
+ scoped_refptr<QuotaManager> quota_manager_;
+ MockStorageClient* client_;
+};
+
+// This test simulates a usage change in a quota client and verifies that a
+// storage observer will receive a storage event.
+TEST_F(StorageMonitorIntegrationTest, NotifyUsageEvent) {
+ const StorageType kTestStorageType = kStorageTypePersistent;
+ const int64_t kTestUsage = 234743;
+
+ // Register the observer.
+ StorageObserver::MonitorParams params(kTestStorageType,
+ GURL(kDefaultOrigin),
+ base::TimeDelta::FromHours(1),
+ false);
+ MockObserver mock_observer;
+ quota_manager_->AddStorageObserver(&mock_observer, params);
+
+ // Fire a usage change.
+ client_->AddOriginAndNotify(GURL(kDefaultOrigin),
+ kTestStorageType,
+ kTestUsage);
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that the observer receives it.
+ ASSERT_EQ(1, mock_observer.EventCount());
+ const StorageObserver::Event& event = mock_observer.LastEvent();
+ EXPECT_EQ(params.filter, event.filter);
+ EXPECT_EQ(kTestUsage, event.usage);
+}
+
+} // namespace content
diff --git a/chromium/storage/browser/quota/usage_tracker_unittest.cc b/chromium/storage/browser/quota/usage_tracker_unittest.cc
new file mode 100644
index 00000000000..86c1cfaa51a
--- /dev/null
+++ b/chromium/storage/browser/quota/usage_tracker_unittest.cc
@@ -0,0 +1,339 @@
+// 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 <stdint.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/url_util.h"
+#include "storage/browser/quota/usage_tracker.h"
+#include "storage/browser/test/mock_special_storage_policy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using storage::kQuotaStatusOk;
+using storage::kStorageTypeTemporary;
+using storage::QuotaClient;
+using storage::QuotaClientList;
+using storage::SpecialStoragePolicy;
+using storage::StorageType;
+using storage::UsageTracker;
+
+namespace content {
+
+namespace {
+
+void DidGetGlobalUsage(bool* done,
+ int64_t* usage_out,
+ int64_t* unlimited_usage_out,
+ int64_t usage,
+ int64_t unlimited_usage) {
+ EXPECT_FALSE(*done);
+ *done = true;
+ *usage_out = usage;
+ *unlimited_usage_out = unlimited_usage;
+}
+
+void DidGetUsage(bool* done, int64_t* usage_out, int64_t usage) {
+ EXPECT_FALSE(*done);
+ *done = true;
+ *usage_out = usage;
+}
+
+} // namespace
+
+class MockQuotaClient : public QuotaClient {
+ public:
+ MockQuotaClient() {}
+ ~MockQuotaClient() override {}
+
+ ID id() const override { return kFileSystem; }
+
+ void OnQuotaManagerDestroyed() override {}
+
+ void GetOriginUsage(const GURL& origin,
+ StorageType type,
+ const GetUsageCallback& callback) override {
+ EXPECT_EQ(kStorageTypeTemporary, type);
+ int64_t usage = GetUsage(origin);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, usage));
+ }
+
+ void GetOriginsForType(StorageType type,
+ const GetOriginsCallback& callback) override {
+ EXPECT_EQ(kStorageTypeTemporary, type);
+ std::set<GURL> origins;
+ for (UsageMap::const_iterator itr = usage_map_.begin();
+ itr != usage_map_.end(); ++itr) {
+ origins.insert(itr->first);
+ }
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, origins));
+ }
+
+ void GetOriginsForHost(StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) override {
+ EXPECT_EQ(kStorageTypeTemporary, type);
+ std::set<GURL> origins;
+ for (UsageMap::const_iterator itr = usage_map_.begin();
+ itr != usage_map_.end(); ++itr) {
+ if (net::GetHostOrSpecFromURL(itr->first) == host)
+ origins.insert(itr->first);
+ }
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, origins));
+ }
+
+ void DeleteOriginData(const GURL& origin,
+ StorageType type,
+ const DeletionCallback& callback) override {
+ EXPECT_EQ(kStorageTypeTemporary, type);
+ usage_map_.erase(origin);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, kQuotaStatusOk));
+ }
+
+ bool DoesSupport(storage::StorageType type) const override {
+ return type == storage::kStorageTypeTemporary;
+ }
+
+ int64_t GetUsage(const GURL& origin) {
+ UsageMap::const_iterator found = usage_map_.find(origin);
+ if (found == usage_map_.end())
+ return 0;
+ return found->second;
+ }
+
+ void SetUsage(const GURL& origin, int64_t usage) {
+ usage_map_[origin] = usage;
+ }
+
+ int64_t UpdateUsage(const GURL& origin, int64_t delta) {
+ return usage_map_[origin] += delta;
+ }
+
+ private:
+ typedef std::map<GURL, int64_t> UsageMap;
+
+ UsageMap usage_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockQuotaClient);
+};
+
+class UsageTrackerTest : public testing::Test {
+ public:
+ UsageTrackerTest()
+ : storage_policy_(new MockSpecialStoragePolicy()),
+ usage_tracker_(GetUsageTrackerList(), kStorageTypeTemporary,
+ storage_policy_.get(), NULL) {
+ }
+
+ ~UsageTrackerTest() override {}
+
+ UsageTracker* usage_tracker() {
+ return &usage_tracker_;
+ }
+
+ void UpdateUsage(const GURL& origin, int64_t delta) {
+ quota_client_.UpdateUsage(origin, delta);
+ usage_tracker_.UpdateUsageCache(quota_client_.id(), origin, delta);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void UpdateUsageWithoutNotification(const GURL& origin, int64_t delta) {
+ quota_client_.UpdateUsage(origin, delta);
+ }
+
+ void GetGlobalLimitedUsage(int64_t* limited_usage) {
+ bool done = false;
+ usage_tracker_.GetGlobalLimitedUsage(base::Bind(
+ &DidGetUsage, &done, limited_usage));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(done);
+ }
+
+ void GetGlobalUsage(int64_t* usage, int64_t* unlimited_usage) {
+ bool done = false;
+ usage_tracker_.GetGlobalUsage(base::Bind(
+ &DidGetGlobalUsage,
+ &done, usage, unlimited_usage));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(done);
+ }
+
+ void GetHostUsage(const std::string& host, int64_t* usage) {
+ bool done = false;
+ usage_tracker_.GetHostUsage(host, base::Bind(&DidGetUsage, &done, usage));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(done);
+ }
+
+ void GrantUnlimitedStoragePolicy(const GURL& origin) {
+ if (!storage_policy_->IsStorageUnlimited(origin)) {
+ storage_policy_->AddUnlimited(origin);
+ storage_policy_->NotifyGranted(
+ origin, SpecialStoragePolicy::STORAGE_UNLIMITED);
+ }
+ }
+
+ void RevokeUnlimitedStoragePolicy(const GURL& origin) {
+ if (storage_policy_->IsStorageUnlimited(origin)) {
+ storage_policy_->RemoveUnlimited(origin);
+ storage_policy_->NotifyRevoked(
+ origin, SpecialStoragePolicy::STORAGE_UNLIMITED);
+ }
+ }
+
+ void SetUsageCacheEnabled(const GURL& origin, bool enabled) {
+ usage_tracker_.SetUsageCacheEnabled(
+ quota_client_.id(), origin, enabled);
+ }
+
+ private:
+ QuotaClientList GetUsageTrackerList() {
+ QuotaClientList client_list;
+ client_list.push_back(&quota_client_);
+ return client_list;
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
+ MockQuotaClient quota_client_;
+ UsageTracker usage_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsageTrackerTest);
+};
+
+TEST_F(UsageTrackerTest, GrantAndRevokeUnlimitedStorage) {
+ int64_t usage = 0;
+ int64_t unlimited_usage = 0;
+ int64_t host_usage = 0;
+ GetGlobalUsage(&usage, &unlimited_usage);
+ EXPECT_EQ(0, usage);
+ EXPECT_EQ(0, unlimited_usage);
+
+ const GURL origin("http://example.com");
+ const std::string host(net::GetHostOrSpecFromURL(origin));
+
+ UpdateUsage(origin, 100);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(100, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(100, host_usage);
+
+ GrantUnlimitedStoragePolicy(origin);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(100, usage);
+ EXPECT_EQ(100, unlimited_usage);
+ EXPECT_EQ(100, host_usage);
+
+ RevokeUnlimitedStoragePolicy(origin);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(100, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(100, host_usage);
+}
+
+TEST_F(UsageTrackerTest, CacheDisabledClientTest) {
+ int64_t usage = 0;
+ int64_t unlimited_usage = 0;
+ int64_t host_usage = 0;
+
+ const GURL origin("http://example.com");
+ const std::string host(net::GetHostOrSpecFromURL(origin));
+
+ UpdateUsage(origin, 100);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(100, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(100, host_usage);
+
+ UpdateUsageWithoutNotification(origin, 100);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(100, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(100, host_usage);
+
+ GrantUnlimitedStoragePolicy(origin);
+ UpdateUsageWithoutNotification(origin, 100);
+ SetUsageCacheEnabled(origin, false);
+ UpdateUsageWithoutNotification(origin, 100);
+
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(400, usage);
+ EXPECT_EQ(400, unlimited_usage);
+ EXPECT_EQ(400, host_usage);
+
+ RevokeUnlimitedStoragePolicy(origin);
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(400, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(400, host_usage);
+
+ SetUsageCacheEnabled(origin, true);
+ UpdateUsage(origin, 100);
+
+ GetGlobalUsage(&usage, &unlimited_usage);
+ GetHostUsage(host, &host_usage);
+ EXPECT_EQ(500, usage);
+ EXPECT_EQ(0, unlimited_usage);
+ EXPECT_EQ(500, host_usage);
+}
+
+TEST_F(UsageTrackerTest, LimitedGlobalUsageTest) {
+ const GURL kNormal("http://normal");
+ const GURL kUnlimited("http://unlimited");
+ const GURL kNonCached("http://non_cached");
+ const GURL kNonCachedUnlimited("http://non_cached-unlimited");
+
+ GrantUnlimitedStoragePolicy(kUnlimited);
+ GrantUnlimitedStoragePolicy(kNonCachedUnlimited);
+
+ SetUsageCacheEnabled(kNonCached, false);
+ SetUsageCacheEnabled(kNonCachedUnlimited, false);
+
+ UpdateUsageWithoutNotification(kNormal, 1);
+ UpdateUsageWithoutNotification(kUnlimited, 2);
+ UpdateUsageWithoutNotification(kNonCached, 4);
+ UpdateUsageWithoutNotification(kNonCachedUnlimited, 8);
+
+ int64_t limited_usage = 0;
+ int64_t total_usage = 0;
+ int64_t unlimited_usage = 0;
+
+ GetGlobalLimitedUsage(&limited_usage);
+ GetGlobalUsage(&total_usage, &unlimited_usage);
+ EXPECT_EQ(1 + 4, limited_usage);
+ EXPECT_EQ(1 + 2 + 4 + 8, total_usage);
+ EXPECT_EQ(2 + 8, unlimited_usage);
+
+ UpdateUsageWithoutNotification(kNonCached, 16 - 4);
+ UpdateUsageWithoutNotification(kNonCachedUnlimited, 32 - 8);
+
+ GetGlobalLimitedUsage(&limited_usage);
+ GetGlobalUsage(&total_usage, &unlimited_usage);
+ EXPECT_EQ(1 + 16, limited_usage);
+ EXPECT_EQ(1 + 2 + 16 + 32, total_usage);
+ EXPECT_EQ(2 + 32, unlimited_usage);
+}
+
+
+} // namespace content
diff --git a/chromium/storage/common/BUILD.gn b/chromium/storage/common/BUILD.gn
index 1f15f489857..5f1b5e3b7f3 100644
--- a/chromium/storage/common/BUILD.gn
+++ b/chromium/storage/common/BUILD.gn
@@ -29,6 +29,8 @@ component("common") {
"quota/quota_status_code.h",
"quota/quota_types.h",
"storage_common_export.h",
+ "storage_histograms.cc",
+ "storage_histograms.h",
]
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
@@ -47,3 +49,21 @@ component("common") {
"//url",
]
}
+
+source_set("unittests") {
+ testonly = true
+
+ sources = [
+ "database/database_connections_unittest.cc",
+ "database/database_identifier_unittest.cc",
+ "fileapi/file_system_util_unittest.cc",
+ ]
+
+ deps = [
+ ":common",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//url",
+ ]
+}
diff --git a/chromium/storage/common/blob_storage/OWNERS b/chromium/storage/common/blob_storage/OWNERS
index 471c08e8a01..18f6cd219a4 100644
--- a/chromium/storage/common/blob_storage/OWNERS
+++ b/chromium/storage/common/blob_storage/OWNERS
@@ -1 +1,4 @@
dmurph@chromium.org
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>FileAPI
diff --git a/chromium/storage/common/blob_storage/blob_storage_constants.h b/chromium/storage/common/blob_storage/blob_storage_constants.h
index a606397eede..4a4e207a9cd 100644
--- a/chromium/storage/common/blob_storage/blob_storage_constants.h
+++ b/chromium/storage/common/blob_storage/blob_storage_constants.h
@@ -9,6 +9,7 @@
#include <stdint.h>
#include "base/callback_forward.h"
+#include "build/build_config.h"
#include "storage/common/storage_common_export.h"
namespace storage {
@@ -17,9 +18,17 @@ constexpr size_t kDefaultIPCMemorySize = 250u * 1024;
constexpr size_t kDefaultSharedMemorySize = 10u * 1024 * 1024;
constexpr size_t kDefaultMaxBlobInMemorySpace = 500u * 1024 * 1024;
constexpr uint64_t kDefaultMaxBlobDiskSpace = 0ull;
-constexpr uint64_t kDefaultMinPageFileSize = 5ull * 1024 * 1024;
constexpr uint64_t kDefaultMaxPageFileSize = 100ull * 1024 * 1024;
+#if defined(OS_ANDROID)
+// On minimal Android maximum in-memory space can be as low as 5MB.
+constexpr uint64_t kDefaultMinPageFileSize = 5ull * 1024 * 1024 / 2;
+const float kDefaultMaxBlobInMemorySpaceUnderPressureRatio = 0.02f;
+#else
+constexpr uint64_t kDefaultMinPageFileSize = 5ull * 1024 * 1024;
+const float kDefaultMaxBlobInMemorySpaceUnderPressureRatio = 0.002f;
+#endif
+
// All sizes are in bytes.
struct STORAGE_COMMON_EXPORT BlobStorageLimits {
// Returns if the current configuration is valid.
@@ -45,6 +54,12 @@ struct STORAGE_COMMON_EXPORT BlobStorageLimits {
// This is the maximum amount of memory we can use to store blobs.
size_t max_blob_in_memory_space = kDefaultMaxBlobInMemorySpace;
+ // The ratio applied to |max_blob_in_memory_space| to reduce memory usage
+ // under memory pressure. Note: Under pressure we modify the
+ // |min_page_file_size| to ensure we can evict items until we get below the
+ // reduced memory limit.
+ float max_blob_in_memory_space_under_pressure_ratio =
+ kDefaultMaxBlobInMemorySpaceUnderPressureRatio;
// This is the maximum amount of disk space we can use.
uint64_t desired_max_disk_space = kDefaultMaxBlobDiskSpace;
diff --git a/chromium/storage/common/database/OWNERS b/chromium/storage/common/database/OWNERS
new file mode 100644
index 00000000000..50969733054
--- /dev/null
+++ b/chromium/storage/common/database/OWNERS
@@ -0,0 +1,2 @@
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>WebSQL
diff --git a/chromium/storage/common/database/database_connections_unittest.cc b/chromium/storage/common/database/database_connections_unittest.cc
new file mode 100644
index 00000000000..c159a5dcd62
--- /dev/null
+++ b/chromium/storage/common/database/database_connections_unittest.cc
@@ -0,0 +1,129 @@
+// 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 <stdint.h>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "storage/common/database/database_connections.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ASCIIToUTF16;
+using storage::DatabaseConnections;
+using storage::DatabaseConnectionsWrapper;
+
+namespace content {
+
+namespace {
+
+void RemoveConnectionTask(
+ const std::string& origin_id, const base::string16& database_name,
+ scoped_refptr<DatabaseConnectionsWrapper> obj,
+ bool* did_task_execute) {
+ *did_task_execute = true;
+ obj->RemoveOpenConnection(origin_id, database_name);
+}
+
+} // anonymous namespace
+
+TEST(DatabaseConnectionsTest, DatabaseConnectionsTest) {
+ const std::string kOriginId("origin_id");
+ const base::string16 kName(ASCIIToUTF16("database_name"));
+ const base::string16 kName2(ASCIIToUTF16("database_name2"));
+ const int64_t kSize = 1000;
+
+ DatabaseConnections connections;
+
+ EXPECT_TRUE(connections.IsEmpty());
+ EXPECT_FALSE(connections.IsDatabaseOpened(kOriginId, kName));
+ EXPECT_FALSE(connections.IsOriginUsed(kOriginId));
+
+ connections.AddConnection(kOriginId, kName);
+ EXPECT_FALSE(connections.IsEmpty());
+ EXPECT_TRUE(connections.IsDatabaseOpened(kOriginId, kName));
+ EXPECT_TRUE(connections.IsOriginUsed(kOriginId));
+ EXPECT_EQ(0, connections.GetOpenDatabaseSize(kOriginId, kName));
+ connections.SetOpenDatabaseSize(kOriginId, kName, kSize);
+ EXPECT_EQ(kSize, connections.GetOpenDatabaseSize(kOriginId, kName));
+
+ connections.RemoveConnection(kOriginId, kName);
+ EXPECT_TRUE(connections.IsEmpty());
+ EXPECT_FALSE(connections.IsDatabaseOpened(kOriginId, kName));
+ EXPECT_FALSE(connections.IsOriginUsed(kOriginId));
+
+ connections.AddConnection(kOriginId, kName);
+ connections.SetOpenDatabaseSize(kOriginId, kName, kSize);
+ EXPECT_EQ(kSize, connections.GetOpenDatabaseSize(kOriginId, kName));
+ connections.AddConnection(kOriginId, kName);
+ EXPECT_EQ(kSize, connections.GetOpenDatabaseSize(kOriginId, kName));
+ EXPECT_FALSE(connections.IsEmpty());
+ EXPECT_TRUE(connections.IsDatabaseOpened(kOriginId, kName));
+ EXPECT_TRUE(connections.IsOriginUsed(kOriginId));
+ connections.AddConnection(kOriginId, kName2);
+ EXPECT_TRUE(connections.IsDatabaseOpened(kOriginId, kName2));
+
+ DatabaseConnections another;
+ another.AddConnection(kOriginId, kName);
+ another.AddConnection(kOriginId, kName2);
+
+ std::vector<std::pair<std::string, base::string16> > closed_dbs;
+ connections.RemoveConnections(another, &closed_dbs);
+ EXPECT_EQ(1u, closed_dbs.size());
+ EXPECT_EQ(kOriginId, closed_dbs[0].first);
+ EXPECT_EQ(kName2, closed_dbs[0].second);
+ EXPECT_FALSE(connections.IsDatabaseOpened(kOriginId, kName2));
+ EXPECT_TRUE(connections.IsDatabaseOpened(kOriginId, kName));
+ EXPECT_EQ(kSize, connections.GetOpenDatabaseSize(kOriginId, kName));
+ another.RemoveAllConnections();
+ connections.RemoveAllConnections();
+ EXPECT_TRUE(connections.IsEmpty());
+
+ // Ensure the return value properly indicates the initial
+ // addition and final removal.
+ EXPECT_TRUE(connections.AddConnection(kOriginId, kName));
+ EXPECT_FALSE(connections.AddConnection(kOriginId, kName));
+ EXPECT_FALSE(connections.AddConnection(kOriginId, kName));
+ EXPECT_FALSE(connections.RemoveConnection(kOriginId, kName));
+ EXPECT_FALSE(connections.RemoveConnection(kOriginId, kName));
+ EXPECT_TRUE(connections.RemoveConnection(kOriginId, kName));
+}
+
+TEST(DatabaseConnectionsTest, DatabaseConnectionsWrapperTest) {
+ const std::string kOriginId("origin_id");
+ const base::string16 kName(ASCIIToUTF16("database_name"));
+
+ base::MessageLoop message_loop;
+ scoped_refptr<DatabaseConnectionsWrapper> obj(new DatabaseConnectionsWrapper);
+ EXPECT_FALSE(obj->HasOpenConnections());
+ obj->AddOpenConnection(kOriginId, kName);
+ EXPECT_TRUE(obj->HasOpenConnections());
+ obj->AddOpenConnection(kOriginId, kName);
+ EXPECT_TRUE(obj->HasOpenConnections());
+ obj->RemoveOpenConnection(kOriginId, kName);
+ EXPECT_TRUE(obj->HasOpenConnections());
+ obj->RemoveOpenConnection(kOriginId, kName);
+ EXPECT_FALSE(obj->HasOpenConnections());
+ EXPECT_TRUE(obj->WaitForAllDatabasesToClose(base::TimeDelta()));
+
+ // Test WaitForAllDatabasesToClose with the last connection
+ // being removed on another thread.
+ obj->AddOpenConnection(kOriginId, kName);
+ EXPECT_FALSE(obj->WaitForAllDatabasesToClose(base::TimeDelta()));
+ base::Thread thread("WrapperTestThread");
+ thread.Start();
+ bool did_task_execute = false;
+ thread.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RemoveConnectionTask, kOriginId, kName, obj,
+ &did_task_execute));
+ // Use a long timeout value to avoid timeouts on test bots.
+ EXPECT_TRUE(obj->WaitForAllDatabasesToClose(
+ base::TimeDelta::FromSeconds(15)));
+ EXPECT_TRUE(did_task_execute);
+ EXPECT_FALSE(obj->HasOpenConnections());
+}
+
+} // namespace content
diff --git a/chromium/storage/common/database/database_identifier_unittest.cc b/chromium/storage/common/database/database_identifier_unittest.cc
new file mode 100644
index 00000000000..dac125a9bba
--- /dev/null
+++ b/chromium/storage/common/database/database_identifier_unittest.cc
@@ -0,0 +1,287 @@
+// 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 "storage/common/database/database_identifier.h"
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::DatabaseIdentifier;
+
+namespace content {
+namespace {
+
+TEST(DatabaseIdentifierTest, CreateIdentifierFromOrigin) {
+ struct OriginTestCase {
+ std::string origin;
+ std::string expectedIdentifier;
+ } cases[] = {
+ {"http://google.com", "http_google.com_0"},
+ {"http://google.com:80", "http_google.com_0"},
+ {"https://www.google.com", "https_www.google.com_0"},
+ {"https://www.google.com:443", "https_www.google.com_0"},
+ {"http://foo_bar_baz.org", "http_foo_bar_baz.org_0"},
+ {"http://nondefaultport.net:8001", "http_nondefaultport.net_8001"},
+ {"http://invalidportnumber.org:70000", "__0"},
+ {"http://invalidportnumber.org:-6", "__0"},
+ {"http://%E2%98%83.unicode.com", "http_xn--n3h.unicode.com_0"},
+ {"http://\xe2\x98\x83.unicode.com", "http_xn--n3h.unicode.com_0"},
+ {"http://\xf0\x9f\x92\xa9.unicode.com", "http_xn--ls8h.unicode.com_0"},
+ {"file:///", "file__0"},
+ {"data:", "__0"},
+ {"about:blank", "__0"},
+ {"non-standard://foobar.com", "__0"},
+ {"http://[::1]:8080", "http_[__1]_8080"},
+ {"http://[3ffe:2a00:100:7031::1]", "http_[3ffe_2a00_100_7031__1]_0"},
+ {"http://[::ffff:8190:3426]", "http_[__ffff_8190_3426]_0"},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ GURL origin(cases[i].origin);
+ DatabaseIdentifier identifier =
+ DatabaseIdentifier::CreateFromOrigin(origin);
+ EXPECT_EQ(cases[i].expectedIdentifier, identifier.ToString())
+ << "test case " << cases[i].origin;
+ }
+}
+
+// This tests the encoding of a hostname including every character in the range
+// [\x1f, \x80].
+TEST(DatabaseIdentifierTest, CreateIdentifierAllHostChars) {
+ struct Case {
+ std::string hostname;
+ std::string expected;
+ bool shouldRoundTrip;
+ } cases[] = {
+ {"x\x1Fx", "__0", false},
+ {"x\x20x", "http_x%20x_0", false},
+ {"x\x21x", "http_x%21x_0", false},
+ {"x\x22x", "http_x%22x_0", false},
+ {"x\x23x", "http_x_0", false}, // 'x#x', the # and following are ignored.
+ {"x\x24x", "http_x%24x_0", false},
+ {"x\x25x", "__0", false},
+ {"x\x26x", "http_x%26x_0", false},
+ {"x\x27x", "http_x%27x_0", false},
+ {"x\x28x", "http_x%28x_0", false},
+ {"x\x29x", "http_x%29x_0", false},
+ {"x\x2ax", "http_x%2ax_0", false},
+ {"x\x2bx", "http_x+x_0", false},
+ {"x\x2cx", "http_x%2cx_0", false},
+ {"x\x2dx", "http_x-x_0", true},
+ {"x\x2ex", "http_x.x_0", true},
+ {"x\x2fx", "http_x_0", false}, // 'x/x', the / and following are ignored.
+ {"x\x30x", "http_x0x_0", true},
+ {"x\x31x", "http_x1x_0", true},
+ {"x\x32x", "http_x2x_0", true},
+ {"x\x33x", "http_x3x_0", true},
+ {"x\x34x", "http_x4x_0", true},
+ {"x\x35x", "http_x5x_0", true},
+ {"x\x36x", "http_x6x_0", true},
+ {"x\x37x", "http_x7x_0", true},
+ {"x\x38x", "http_x8x_0", true},
+ {"x\x39x", "http_x9x_0", true},
+ {"x\x3ax", "__0", false},
+ {"x\x3bx", "__0", false},
+ {"x\x3cx", "http_x%3cx_0", false},
+ {"x\x3dx", "http_x%3dx_0", false},
+ {"x\x3ex", "http_x%3ex_0", false},
+ {"x\x3fx", "http_x_0", false}, // 'x?x', the ? and following are ignored.
+ {"x\x40x", "http_x_0", false}, // 'x@x', the @ and following are ignored.
+ {"x\x41x", "http_xax_0", true},
+ {"x\x42x", "http_xbx_0", true},
+ {"x\x43x", "http_xcx_0", true},
+ {"x\x44x", "http_xdx_0", true},
+ {"x\x45x", "http_xex_0", true},
+ {"x\x46x", "http_xfx_0", true},
+ {"x\x47x", "http_xgx_0", true},
+ {"x\x48x", "http_xhx_0", true},
+ {"x\x49x", "http_xix_0", true},
+ {"x\x4ax", "http_xjx_0", true},
+ {"x\x4bx", "http_xkx_0", true},
+ {"x\x4cx", "http_xlx_0", true},
+ {"x\x4dx", "http_xmx_0", true},
+ {"x\x4ex", "http_xnx_0", true},
+ {"x\x4fx", "http_xox_0", true},
+ {"x\x50x", "http_xpx_0", true},
+ {"x\x51x", "http_xqx_0", true},
+ {"x\x52x", "http_xrx_0", true},
+ {"x\x53x", "http_xsx_0", true},
+ {"x\x54x", "http_xtx_0", true},
+ {"x\x55x", "http_xux_0", true},
+ {"x\x56x", "http_xvx_0", true},
+ {"x\x57x", "http_xwx_0", true},
+ {"x\x58x", "http_xxx_0", true},
+ {"x\x59x", "http_xyx_0", true},
+ {"x\x5ax", "http_xzx_0", true},
+ {"x\x5bx", "__0", false},
+ {"x\x5cx", "http_x_0", false}, // "x\x", the \ and following are ignored.
+ {"x\x5dx", "__0", false},
+ {"x\x5ex", "__0", false},
+ {"x\x5fx", "http_x_x_0", true},
+ {"x\x60x", "http_x%60x_0", false},
+ {"x\x61x", "http_xax_0", true},
+ {"x\x62x", "http_xbx_0", true},
+ {"x\x63x", "http_xcx_0", true},
+ {"x\x64x", "http_xdx_0", true},
+ {"x\x65x", "http_xex_0", true},
+ {"x\x66x", "http_xfx_0", true},
+ {"x\x67x", "http_xgx_0", true},
+ {"x\x68x", "http_xhx_0", true},
+ {"x\x69x", "http_xix_0", true},
+ {"x\x6ax", "http_xjx_0", true},
+ {"x\x6bx", "http_xkx_0", true},
+ {"x\x6cx", "http_xlx_0", true},
+ {"x\x6dx", "http_xmx_0", true},
+ {"x\x6ex", "http_xnx_0", true},
+ {"x\x6fx", "http_xox_0", true},
+ {"x\x70x", "http_xpx_0", true},
+ {"x\x71x", "http_xqx_0", true},
+ {"x\x72x", "http_xrx_0", true},
+ {"x\x73x", "http_xsx_0", true},
+ {"x\x74x", "http_xtx_0", true},
+ {"x\x75x", "http_xux_0", true},
+ {"x\x76x", "http_xvx_0", true},
+ {"x\x77x", "http_xwx_0", true},
+ {"x\x78x", "http_xxx_0", true},
+ {"x\x79x", "http_xyx_0", true},
+ {"x\x7ax", "http_xzx_0", true},
+ {"x\x7bx", "http_x%7bx_0", false},
+ {"x\x7cx", "http_x%7cx_0", false},
+ {"x\x7dx", "http_x%7dx_0", false},
+ {"x\x7ex", "__0", false},
+ {"x\x7fx", "__0", false},
+ {"x\x80x", "__0", false},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ GURL origin("http://" + cases[i].hostname);
+ DatabaseIdentifier identifier =
+ DatabaseIdentifier::CreateFromOrigin(origin);
+ EXPECT_EQ(cases[i].expected, identifier.ToString())
+ << "test case " << i << " :\"" << cases[i].hostname << "\"";
+ if (cases[i].shouldRoundTrip) {
+ DatabaseIdentifier parsed_identifier =
+ DatabaseIdentifier::Parse(identifier.ToString());
+ EXPECT_EQ(identifier.ToString(), parsed_identifier.ToString())
+ << "test case " << i << " :\"" << cases[i].hostname << "\"";
+ }
+ }
+}
+
+TEST(DatabaseIdentifierTest, ExtractOriginDataFromIdentifier) {
+ struct IdentifierTestCase {
+ std::string str;
+ std::string expected_scheme;
+ std::string expected_host;
+ int expected_port;
+ GURL expected_origin;
+ bool expected_unique;
+ };
+
+ IdentifierTestCase valid_cases[] = {
+ {"http_google.com_0",
+ "http", "google.com", 0, GURL("http://google.com"), false},
+ {"https_google.com_0",
+ "https", "google.com", 0, GURL("https://google.com"), false},
+ {"ftp_google.com_0",
+ "ftp", "google.com", 0, GURL("ftp://google.com"), false},
+ {"unknown_google.com_0",
+ "unknown", "", 0, GURL("unknown://"), false},
+ {"http_nondefaultport.net_8001",
+ "http", "nondefaultport.net", 8001,
+ GURL("http://nondefaultport.net:8001"), false},
+ {"file__0",
+ "", "", 0, GURL("file:///"), true},
+ {"__0",
+ "", "", 0, GURL(), true},
+ {"http_foo_bar_baz.org_0",
+ "http", "foo_bar_baz.org", 0, GURL("http://foo_bar_baz.org"), false},
+ {"http_xn--n3h.unicode.com_0",
+ "http", "xn--n3h.unicode.com", 0,
+ GURL("http://xn--n3h.unicode.com"), false},
+ {"http_dot.com_0", "http", "dot.com", 0, GURL("http://dot.com"), false},
+ {"http_escaped%3Dfun.com_0", "http", "escaped%3dfun.com", 0,
+ GURL("http://escaped%3dfun.com"), false},
+ {"http_[__1]_8080",
+ "http", "[::1]", 8080, GURL("http://[::1]:8080"), false},
+ {"http_[3ffe_2a00_100_7031__1]_0",
+ "http", "[3ffe:2a00:100:7031::1]", 0,
+ GURL("http://[3ffe:2a00:100:7031::1]"), false},
+ {"http_[__ffff_8190_3426]_0",
+ "http", "[::ffff:8190:3426]", 0, GURL("http://[::ffff:8190:3426]"), false},
+ };
+
+ for (size_t i = 0; i < arraysize(valid_cases); ++i) {
+ DatabaseIdentifier identifier =
+ DatabaseIdentifier::Parse(valid_cases[i].str);
+ EXPECT_EQ(valid_cases[i].expected_scheme, identifier.scheme())
+ << "test case " << valid_cases[i].str;
+ EXPECT_EQ(valid_cases[i].expected_host, identifier.hostname())
+ << "test case " << valid_cases[i].str;
+ EXPECT_EQ(valid_cases[i].expected_port, identifier.port())
+ << "test case " << valid_cases[i].str;
+ EXPECT_EQ(valid_cases[i].expected_origin, identifier.ToOrigin())
+ << "test case " << valid_cases[i].str;
+ EXPECT_EQ(valid_cases[i].expected_unique, identifier.is_unique())
+ << "test case " << valid_cases[i].str;
+ }
+
+ std::string bogus_components[] = {
+ "", "_", "__", std::string("\x00", 1), std::string("http_\x00_0", 8),
+ "ht\x7ctp_badscheme.com_0", "http_unescaped_percent_%.com_0",
+ "http_port_too_big.net_75000", "http_port_too_small.net_-25",
+ "http_shouldbeescaped\x7c.com_0", "http_latin1\x8a.org_8001",
+ "http_\xe2\x98\x83.unicode.com_0",
+ "http_dot%252ecom_0",
+ "HtTp_NonCanonicalRepresenTation_0",
+ "http_non_ascii.\xa1.com_0",
+ "http_not_canonical_escape%3d_0",
+ "http_bytes_after_port_0abcd",
+ };
+
+ for (size_t i = 0; i < arraysize(bogus_components); ++i) {
+ DatabaseIdentifier identifier =
+ DatabaseIdentifier::Parse(bogus_components[i]);
+ EXPECT_EQ("__0", identifier.ToString())
+ << "test case " << bogus_components[i];
+ EXPECT_EQ(GURL("null"), identifier.ToOrigin())
+ << "test case " << bogus_components[i];
+ EXPECT_EQ(true, identifier.is_unique())
+ << "test case " << bogus_components[i];
+ }
+}
+
+static GURL ToAndFromOriginIdentifier(const GURL origin_url) {
+ std::string id = storage::GetIdentifierFromOrigin(origin_url);
+ return storage::GetOriginFromIdentifier(id);
+}
+
+static void TestValidOriginIdentifier(bool expected_result,
+ const std::string& id) {
+ EXPECT_EQ(expected_result,
+ storage::IsValidOriginIdentifier(id));
+}
+
+TEST(DatabaseIdentifierTest, OriginIdentifiers) {
+ const GURL kFileOrigin(GURL("file:///").GetOrigin());
+ const GURL kHttpOrigin(GURL("http://bar/").GetOrigin());
+ EXPECT_EQ(kFileOrigin, ToAndFromOriginIdentifier(kFileOrigin));
+ EXPECT_EQ(kHttpOrigin, ToAndFromOriginIdentifier(kHttpOrigin));
+}
+
+TEST(DatabaseIdentifierTest, IsValidOriginIdentifier) {
+ TestValidOriginIdentifier(true, "http_bar_0");
+ TestValidOriginIdentifier(false, "");
+ TestValidOriginIdentifier(false, "bad..id");
+ TestValidOriginIdentifier(false, "bad/id");
+ TestValidOriginIdentifier(false, "bad\\id");
+ TestValidOriginIdentifier(false, "http_bad:0_2");
+ TestValidOriginIdentifier(false, std::string("bad\0id", 6));
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/storage/common/fileapi/file_system_util_unittest.cc b/chromium/storage/common/fileapi/file_system_util_unittest.cc
new file mode 100644
index 00000000000..4a617d59b0d
--- /dev/null
+++ b/chromium/storage/common/fileapi/file_system_util_unittest.cc
@@ -0,0 +1,310 @@
+// 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 "storage/common/fileapi/file_system_util.h"
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using storage::CrackIsolatedFileSystemName;
+using storage::GetExternalFileSystemRootURIString;
+using storage::GetIsolatedFileSystemName;
+using storage::GetIsolatedFileSystemRootURIString;
+using storage::ValidateIsolatedFileSystemId;
+using storage::VirtualPath;
+
+namespace content {
+namespace {
+
+class FileSystemUtilTest : public testing::Test {};
+
+TEST_F(FileSystemUtilTest, ParseFileSystemSchemeURL) {
+ GURL uri("filesystem:http://chromium.org/temporary/foo/bar");
+ GURL origin_url;
+ storage::FileSystemType type;
+ base::FilePath virtual_path;
+ ParseFileSystemSchemeURL(uri, &origin_url, &type, &virtual_path);
+ EXPECT_EQ(GURL("http://chromium.org"), origin_url);
+ EXPECT_EQ(storage::kFileSystemTypeTemporary, type);
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ base::FilePath expected_path(FILE_PATH_LITERAL("foo\\bar"));
+#else
+ base::FilePath expected_path(FILE_PATH_LITERAL("foo/bar"));
+#endif
+ EXPECT_EQ(expected_path, virtual_path);
+}
+
+TEST_F(FileSystemUtilTest, GetTempFileSystemRootURI) {
+ GURL origin_url("http://chromium.org");
+ storage::FileSystemType type = storage::kFileSystemTypeTemporary;
+ GURL uri = GURL("filesystem:http://chromium.org/temporary/");
+ EXPECT_EQ(uri, GetFileSystemRootURI(origin_url, type));
+}
+
+TEST_F(FileSystemUtilTest, GetPersistentFileSystemRootURI) {
+ GURL origin_url("http://chromium.org");
+ storage::FileSystemType type = storage::kFileSystemTypePersistent;
+ GURL uri = GURL("filesystem:http://chromium.org/persistent/");
+ EXPECT_EQ(uri, GetFileSystemRootURI(origin_url, type));
+}
+
+TEST_F(FileSystemUtilTest, VirtualPathBaseName) {
+ struct test_data {
+ const base::FilePath::StringType path;
+ const base::FilePath::StringType base_name;
+ } test_cases[] = {
+ { FILE_PATH_LITERAL("foo/bar"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("foo/b:bar"), FILE_PATH_LITERAL("b:bar") },
+ { FILE_PATH_LITERAL(""), FILE_PATH_LITERAL("") },
+ { FILE_PATH_LITERAL("/"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("foo//////bar"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("foo/bar/"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("foo/bar/////"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("/bar/////"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("bar/////"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("bar/"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("/bar"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("////bar"), FILE_PATH_LITERAL("bar") },
+ { FILE_PATH_LITERAL("bar"), FILE_PATH_LITERAL("bar") }
+ };
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ base::FilePath input = base::FilePath(test_cases[i].path);
+ base::FilePath base_name = VirtualPath::BaseName(input);
+ EXPECT_EQ(test_cases[i].base_name, base_name.value());
+ }
+}
+
+TEST_F(FileSystemUtilTest, VirtualPathDirName) {
+ struct test_data {
+ const base::FilePath::StringType path;
+ const base::FilePath::StringType dir_name;
+ } test_cases[] = {
+ { FILE_PATH_LITERAL("foo/bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo/b:bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL(""), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("/"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("foo//////bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo/bar/"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo/bar/////"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("/bar/////"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("bar/////"), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("bar/"), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("/bar"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("////bar"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("bar"), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("c:bar"), FILE_PATH_LITERAL(".") },
+#ifdef FILE_PATH_USES_WIN_SEPARATORS
+ { FILE_PATH_LITERAL("foo\\bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo\\b:bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("\\"), FILE_PATH_LITERAL("\\") },
+ { FILE_PATH_LITERAL("foo\\\\\\\\\\\\bar"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo\\bar\\"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("foo\\bar\\\\\\\\\\"), FILE_PATH_LITERAL("foo") },
+ { FILE_PATH_LITERAL("\\bar\\\\\\\\\\"), FILE_PATH_LITERAL("\\") },
+ { FILE_PATH_LITERAL("bar\\\\\\\\\\"), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("bar\\"), FILE_PATH_LITERAL(".") },
+ { FILE_PATH_LITERAL("\\bar"), FILE_PATH_LITERAL("\\") },
+ { FILE_PATH_LITERAL("\\\\\\\\bar"), FILE_PATH_LITERAL("\\") },
+#endif
+ };
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ base::FilePath input = base::FilePath(test_cases[i].path);
+ base::FilePath dir_name = VirtualPath::DirName(input);
+ EXPECT_EQ(test_cases[i].dir_name, dir_name.value());
+ }
+}
+
+TEST_F(FileSystemUtilTest, GetNormalizedFilePath) {
+ struct test_data {
+ const base::FilePath::StringType path;
+ const base::FilePath::StringType normalized_path;
+ } test_cases[] = {
+ { FILE_PATH_LITERAL(""), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("/"), FILE_PATH_LITERAL("/") },
+ { FILE_PATH_LITERAL("foo/bar"), FILE_PATH_LITERAL("/foo/bar") },
+ { FILE_PATH_LITERAL("/foo/bar"), FILE_PATH_LITERAL("/foo/bar") },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { FILE_PATH_LITERAL("\\foo"), FILE_PATH_LITERAL("/foo") },
+#endif
+ };
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ base::FilePath input = base::FilePath(test_cases[i].path);
+ base::FilePath::StringType normalized_path_string =
+ VirtualPath::GetNormalizedFilePath(input);
+ EXPECT_EQ(test_cases[i].normalized_path, normalized_path_string);
+ }
+}
+
+TEST_F(FileSystemUtilTest, IsAbsolutePath) {
+ EXPECT_TRUE(VirtualPath::IsAbsolute(FILE_PATH_LITERAL("/")));
+ EXPECT_TRUE(VirtualPath::IsAbsolute(FILE_PATH_LITERAL("/foo/bar")));
+ EXPECT_FALSE(VirtualPath::IsAbsolute(base::FilePath::StringType()));
+ EXPECT_FALSE(VirtualPath::IsAbsolute(FILE_PATH_LITERAL("foo/bar")));
+}
+
+TEST_F(FileSystemUtilTest, IsRootPath) {
+ EXPECT_TRUE(VirtualPath::IsRootPath(base::FilePath(FILE_PATH_LITERAL(""))));
+ EXPECT_TRUE(VirtualPath::IsRootPath(base::FilePath()));
+ EXPECT_TRUE(VirtualPath::IsRootPath(base::FilePath(FILE_PATH_LITERAL("/"))));
+ EXPECT_TRUE(VirtualPath::IsRootPath(base::FilePath(FILE_PATH_LITERAL("//"))));
+ EXPECT_FALSE(VirtualPath::IsRootPath(
+ base::FilePath(FILE_PATH_LITERAL("c:/"))));
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ EXPECT_TRUE(VirtualPath::IsRootPath(base::FilePath(FILE_PATH_LITERAL("\\"))));
+ EXPECT_FALSE(VirtualPath::IsRootPath(
+ base::FilePath(FILE_PATH_LITERAL("c:\\"))));
+#endif
+}
+
+TEST_F(FileSystemUtilTest, VirtualPathGetComponents) {
+ struct test_data {
+ const base::FilePath::StringType path;
+ size_t count;
+ const base::FilePath::StringType components[2];
+ } test_cases[] = {
+ { FILE_PATH_LITERAL("foo/bar"),
+ 2,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("bar") } },
+ { FILE_PATH_LITERAL("foo"),
+ 1,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("") } },
+ { FILE_PATH_LITERAL("foo////bar"),
+ 2,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("bar") } },
+ { FILE_PATH_LITERAL("foo/c:bar"),
+ 2,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("c:bar") } },
+ { FILE_PATH_LITERAL("c:foo/bar"),
+ 2,
+ { FILE_PATH_LITERAL("c:foo"), FILE_PATH_LITERAL("bar") } },
+ { FILE_PATH_LITERAL("foo/bar"),
+ 2,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("bar") } },
+ { FILE_PATH_LITERAL("/foo/bar"),
+ 2,
+ { FILE_PATH_LITERAL("foo"), FILE_PATH_LITERAL("bar") } },
+ { FILE_PATH_LITERAL("c:/bar"),
+ 2,
+ { FILE_PATH_LITERAL("c:"), FILE_PATH_LITERAL("bar") } },
+#ifdef FILE_PATH_USES_WIN_SEPARATORS
+ { FILE_PATH_LITERAL("c:\\bar"),
+ 2,
+ { FILE_PATH_LITERAL("c:"), FILE_PATH_LITERAL("bar") } },
+#endif
+ };
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ base::FilePath input = base::FilePath(test_cases[i].path);
+ std::vector<base::FilePath::StringType> components;
+ VirtualPath::GetComponents(input, &components);
+ EXPECT_EQ(test_cases[i].count, components.size());
+ for (size_t j = 0; j < components.size(); ++j)
+ EXPECT_EQ(test_cases[i].components[j], components[j]);
+ }
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ base::FilePath input = base::FilePath(test_cases[i].path);
+ std::vector<std::string> components;
+ VirtualPath::GetComponentsUTF8Unsafe(input, &components);
+ EXPECT_EQ(test_cases[i].count, components.size());
+ for (size_t j = 0; j < components.size(); ++j) {
+ EXPECT_EQ(base::FilePath(test_cases[i].components[j]).AsUTF8Unsafe(),
+ components[j]);
+ }
+ }
+}
+
+TEST_F(FileSystemUtilTest, GetIsolatedFileSystemName) {
+ GURL origin_url("http://foo");
+ std::string fsname1 = GetIsolatedFileSystemName(origin_url, "bar");
+ EXPECT_EQ("http_foo_0:Isolated_bar", fsname1);
+}
+
+TEST_F(FileSystemUtilTest, CrackIsolatedFileSystemName) {
+ std::string fsid;
+ EXPECT_TRUE(CrackIsolatedFileSystemName("foo:Isolated_bar", &fsid));
+ EXPECT_EQ("bar", fsid);
+ EXPECT_TRUE(CrackIsolatedFileSystemName("foo:isolated_bar", &fsid));
+ EXPECT_EQ("bar", fsid);
+ EXPECT_TRUE(CrackIsolatedFileSystemName("foo:Isolated__bar", &fsid));
+ EXPECT_EQ("_bar", fsid);
+ EXPECT_TRUE(CrackIsolatedFileSystemName("foo::Isolated_bar", &fsid));
+ EXPECT_EQ("bar", fsid);
+}
+
+TEST_F(FileSystemUtilTest, RejectBadIsolatedFileSystemName) {
+ std::string fsid;
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foobar", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:_bar", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:Isolatedbar", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("fooIsolatedbar", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:Persistent", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:Temporary", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:External", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName(":Isolated_bar", &fsid));
+ EXPECT_FALSE(CrackIsolatedFileSystemName("foo:Isolated_", &fsid));
+}
+
+TEST_F(FileSystemUtilTest, ValidateIsolatedFileSystemId) {
+ EXPECT_TRUE(ValidateIsolatedFileSystemId("ABCDEF0123456789ABCDEF0123456789"));
+ EXPECT_TRUE(ValidateIsolatedFileSystemId("ABCDEFABCDEFABCDEFABCDEFABCDEFAB"));
+ EXPECT_TRUE(ValidateIsolatedFileSystemId("01234567890123456789012345678901"));
+
+ const size_t kExpectedFileSystemIdSize = 32;
+
+ // Should not contain lowercase characters.
+ const std::string kLowercaseId = "abcdef0123456789abcdef0123456789";
+ EXPECT_EQ(kExpectedFileSystemIdSize, kLowercaseId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kLowercaseId));
+
+ // Should not be shorter/longer than expected.
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(std::string()));
+
+ const std::string kShorterId = "ABCDEF0123456789ABCDEF";
+ EXPECT_GT(kExpectedFileSystemIdSize, kShorterId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kShorterId));
+
+ const std::string kLongerId = "ABCDEF0123456789ABCDEF0123456789ABCDEF";
+ EXPECT_LT(kExpectedFileSystemIdSize, kLongerId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kLongerId));
+
+ // Should not contain not alphabetical nor numerical characters.
+ const std::string kSlashId = "ABCD/EFGH/IJKL/MNOP/QRST/UVWX/YZ";
+ EXPECT_EQ(kExpectedFileSystemIdSize, kSlashId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kSlashId));
+
+ const std::string kBackslashId = "ABCD\\EFGH\\IJKL\\MNOP\\QRST\\UVWX\\YZ";
+ EXPECT_EQ(kExpectedFileSystemIdSize, kBackslashId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kBackslashId));
+
+ const std::string kSpaceId = "ABCD EFGH IJKL MNOP QRST UVWX YZ";
+ EXPECT_EQ(kExpectedFileSystemIdSize, kSpaceId.size());
+ EXPECT_FALSE(ValidateIsolatedFileSystemId(kSpaceId));
+}
+
+TEST_F(FileSystemUtilTest, GetIsolatedFileSystemRootURIString) {
+ const GURL kOriginURL("http://foo");
+ // Percents must be escaped, otherwise they will be unintentionally unescaped.
+ const std::string kFileSystemId = "A%20B";
+ const std::string kRootName = "C%20D";
+
+ const std::string url_string =
+ GetIsolatedFileSystemRootURIString(kOriginURL, kFileSystemId, kRootName);
+ EXPECT_EQ("filesystem:http://foo/isolated/A%2520B/C%2520D/", url_string);
+}
+
+TEST_F(FileSystemUtilTest, GetExternalFileSystemRootURIString) {
+ const GURL kOriginURL("http://foo");
+ // Percents must be escaped, otherwise they will be unintentionally unescaped.
+ const std::string kMountName = "X%20Y";
+
+ const std::string url_string =
+ GetExternalFileSystemRootURIString(kOriginURL, kMountName);
+ EXPECT_EQ("filesystem:http://foo/external/X%2520Y/", url_string);
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/storage/common/quota/OWNERS b/chromium/storage/common/quota/OWNERS
new file mode 100644
index 00000000000..8c196cc4268
--- /dev/null
+++ b/chromium/storage/common/quota/OWNERS
@@ -0,0 +1,2 @@
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>Quota
diff --git a/chromium/storage/common/storage_histograms.cc b/chromium/storage/common/storage_histograms.cc
new file mode 100644
index 00000000000..fdae1a63358
--- /dev/null
+++ b/chromium/storage/common/storage_histograms.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 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 "storage/common/storage_histograms.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace storage {
+
+void RecordBytesWritten(const char* label, int amount) {
+ const std::string name = "Storage.BytesWritten.";
+ base::UmaHistogramCounts10M(name + label, amount);
+}
+
+void RecordBytesRead(const char* label, int amount) {
+ const std::string name = "Storage.BytesRead.";
+ base::UmaHistogramCounts10M(name + label, amount);
+}
+
+} // namespace storage
diff --git a/chromium/storage/common/storage_histograms.h b/chromium/storage/common/storage_histograms.h
new file mode 100644
index 00000000000..8cde5c2c1cb
--- /dev/null
+++ b/chromium/storage/common/storage_histograms.h
@@ -0,0 +1,18 @@
+// Copyright 2017 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 STORAGE_COMMON_HISTOGRAMS_H
+#define STORAGE_COMMON_HISTOGRAMS_H
+
+#include <string>
+#include "storage/common/storage_common_export.h"
+
+namespace storage {
+
+STORAGE_COMMON_EXPORT void RecordBytesWritten(const char* label, int bytes);
+STORAGE_COMMON_EXPORT void RecordBytesRead(const char* label, int bytes);
+
+} // namespace storage
+
+#endif // STORAGE_COMMON_HISTOGRAMS_H