diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-17 13:57:45 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-19 13:44:40 +0000 |
commit | 6ec7b8da05d21a3878bd21c691b41e675d74bb1c (patch) | |
tree | b87f250bc19413750b9bb9cdbf2da20ef5014820 /chromium/storage | |
parent | ec02ee4181c49b61fce1c8fb99292dbb8139cc90 (diff) | |
download | qtwebengine-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')
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, "a); + 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_, + "a); + 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, "a)); + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, "a)); + + // Insert quota for temporary. + EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota1)); + EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + EXPECT_EQ(kQuota1, quota); + + // Update quota for temporary. + EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota2)); + EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + EXPECT_EQ(kQuota2, quota); + + // Quota for persistent must not be updated. + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, "a)); + + // Delete temporary storage quota. + EXPECT_TRUE(db.DeleteHostQuota(kHost, kStorageTypeTemporary)); + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + } + + 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("a_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 |