// 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 "webkit/browser/fileapi/quota/quota_reservation_manager.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/browser/fileapi/quota/open_file_handle.h" #include "webkit/browser/fileapi/quota/quota_reservation.h" namespace fileapi { namespace { const char kOrigin[] = "http://example.com"; const FileSystemType kType = kFileSystemTypeTemporary; const int64 kInitialFileSize = 30; typedef QuotaReservationManager::ReserveQuotaCallback ReserveQuotaCallback; class FakeBackend : public QuotaReservationManager::QuotaBackend { public: FakeBackend() : on_memory_usage_(0), on_disk_usage_(0) {} virtual ~FakeBackend() {} virtual void ReserveQuota(const GURL& origin, FileSystemType type, int64 delta, const ReserveQuotaCallback& callback) OVERRIDE { EXPECT_EQ(GURL(kOrigin), origin); EXPECT_EQ(kType, type); on_memory_usage_ += delta; base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(base::IgnoreResult(callback), base::PLATFORM_FILE_OK)); } virtual void ReleaseReservedQuota(const GURL& origin, FileSystemType type, int64 size) OVERRIDE { EXPECT_LE(0, size); EXPECT_EQ(GURL(kOrigin), origin); EXPECT_EQ(kType, type); on_memory_usage_ -= size; } virtual void CommitQuotaUsage(const GURL& origin, FileSystemType type, int64 delta) OVERRIDE { EXPECT_EQ(GURL(kOrigin), origin); EXPECT_EQ(kType, type); on_disk_usage_ += delta; } virtual void IncrementDirtyCount(const GURL& origin, FileSystemType type) OVERRIDE {} virtual void DecrementDirtyCount(const GURL& origin, FileSystemType type) OVERRIDE {} int64 on_memory_usage() { return on_memory_usage_; } int64 on_disk_usage() { return on_disk_usage_; } private: int64 on_memory_usage_; int64 on_disk_usage_; DISALLOW_COPY_AND_ASSIGN(FakeBackend); }; void ExpectSuccess(bool* done, base::PlatformFileError error) { EXPECT_FALSE(*done); *done = true; EXPECT_EQ(base::PLATFORM_FILE_OK, error); } void RefreshQuota(QuotaReservation* reservation, int64 size) { DCHECK(reservation); bool done = false; reservation->RefreshReservation(size, base::Bind(&ExpectSuccess, &done)); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(done); } } // namespace class QuotaReservationManagerTest : public testing::Test { public: QuotaReservationManagerTest() {} virtual ~QuotaReservationManagerTest() {} virtual void SetUp() OVERRIDE { ASSERT_TRUE(work_dir_.CreateUniqueTempDir()); file_path_ = work_dir_.path().Append(FILE_PATH_LITERAL("hoge")); SetFileSize(kInitialFileSize); scoped_ptr<QuotaReservationManager::QuotaBackend> backend(new FakeBackend); reservation_manager_.reset(new QuotaReservationManager(backend.Pass())); } virtual void TearDown() OVERRIDE { reservation_manager_.reset(); } int64 GetFileSize() { int64 size = 0; base::GetFileSize(file_path_, &size); return size; } void SetFileSize(int64 size) { bool created = false; base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; base::PlatformFile file = CreatePlatformFile( file_path_, base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE, &created, &error); ASSERT_EQ(base::PLATFORM_FILE_OK, error); ASSERT_TRUE(base::TruncatePlatformFile(file, size)); ASSERT_TRUE(base::ClosePlatformFile(file)); } void ExtendFileTo(int64 size) { if (GetFileSize() < size) SetFileSize(size); } FakeBackend* fake_backend() { return static_cast<FakeBackend*>(reservation_manager_->backend_.get()); } QuotaReservationManager* reservation_manager() { return reservation_manager_.get(); } const base::FilePath& file_path() const { return file_path_; } private: base::MessageLoop message_loop_; base::ScopedTempDir work_dir_; base::FilePath file_path_; scoped_ptr<QuotaReservationManager> reservation_manager_; DISALLOW_COPY_AND_ASSIGN(QuotaReservationManagerTest); }; TEST_F(QuotaReservationManagerTest, BasicTest) { GURL origin(kOrigin); FileSystemType type = kType; // Create Reservation channel for the origin and type. // Reservation holds remaining quota reservation and provides a method to // refresh it. scoped_refptr<QuotaReservation> reservation = reservation_manager()->CreateReservation(origin, type); EXPECT_EQ(0, reservation->remaining_quota()); RefreshQuota(reservation, 100); EXPECT_EQ(100, reservation->remaining_quota()); { // For each open file for write, the client should create OpenFileHandle // object. // It's OK to create multiple OpenFileHandle for single file. scoped_ptr<OpenFileHandle> open_file = reservation->GetOpenFileHandle(file_path()); // Before reserved quota ran out, the client can perform any number of // operation for the file. // The client should calculate how much quota is consumed by itself. int64 remaining_quota = reservation->remaining_quota(); int64 base_file_size = open_file->base_file_size(); int64 max_written_offset = base_file_size; ExtendFileTo(90); max_written_offset = 90; remaining_quota -= max_written_offset - base_file_size; // When the reserved quota ran out, the client can request quota refresh // through Reservation. Before requesting another portion of quota, the // client should report maximum written offset for each modified files. open_file->UpdateMaxWrittenOffset(max_written_offset); EXPECT_EQ(remaining_quota, reservation->remaining_quota()); RefreshQuota(reservation, 100); EXPECT_EQ(100, reservation->remaining_quota()); } EXPECT_EQ(90, GetFileSize()); EXPECT_EQ(100, fake_backend()->on_memory_usage()); EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); reservation = NULL; EXPECT_EQ(90, GetFileSize()); EXPECT_EQ(0, fake_backend()->on_memory_usage()); EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); } TEST_F(QuotaReservationManagerTest, MultipleWriter) { GURL origin(kOrigin); FileSystemType type = kType; scoped_refptr<QuotaReservation> reservation = reservation_manager()->CreateReservation(origin, type); EXPECT_EQ(0, reservation->remaining_quota()); RefreshQuota(reservation, 100); EXPECT_EQ(100, reservation->remaining_quota()); { scoped_ptr<OpenFileHandle> open_file1 = reservation->GetOpenFileHandle(file_path()); scoped_ptr<OpenFileHandle> open_file2 = reservation->GetOpenFileHandle(file_path()); int64 remaining_quota = reservation->remaining_quota(); int64 base_file_size_for_file1 = open_file1->base_file_size(); int64 max_written_offset_for_file1 = base_file_size_for_file1; int64 base_file_size_for_file2 = open_file2->base_file_size(); int64 max_written_offset_for_file2 = base_file_size_for_file2; // Each writer should maintain max_written_offset and base_file_size // independently even if there are multiple writers for the same file. max_written_offset_for_file1 = 50; ExtendFileTo(max_written_offset_for_file1); remaining_quota -= max_written_offset_for_file1 - base_file_size_for_file1; base_file_size_for_file1 = max_written_offset_for_file1; max_written_offset_for_file2 = 90; ExtendFileTo(max_written_offset_for_file2); remaining_quota -= max_written_offset_for_file2 - base_file_size_for_file2; base_file_size_for_file2 = max_written_offset_for_file2; // Before requesting quota refresh, each writer should report their // maximum_written_offset. UpdateMaxWrittenOffset returns updated // base_file_size that the writer should calculate quota consumption based // on that. base_file_size_for_file1 = open_file1->UpdateMaxWrittenOffset(max_written_offset_for_file1); max_written_offset_for_file1 = base_file_size_for_file1; EXPECT_EQ(100 - (50 - kInitialFileSize), reservation->remaining_quota()); base_file_size_for_file2 = open_file2->UpdateMaxWrittenOffset(max_written_offset_for_file2); max_written_offset_for_file2 = base_file_size_for_file2; EXPECT_EQ(100 - (50 - kInitialFileSize) - (90 - 50), reservation->remaining_quota()); RefreshQuota(reservation, 100); EXPECT_EQ(100, reservation->remaining_quota()); } EXPECT_EQ(90, GetFileSize()); EXPECT_EQ(100, fake_backend()->on_memory_usage()); EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); reservation = NULL; EXPECT_EQ(90, GetFileSize()); EXPECT_EQ(0, fake_backend()->on_memory_usage()); EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); } TEST_F(QuotaReservationManagerTest, MultipleClient) { GURL origin(kOrigin); FileSystemType type = kType; scoped_refptr<QuotaReservation> reservation1 = reservation_manager()->CreateReservation(origin, type); EXPECT_EQ(0, reservation1->remaining_quota()); RefreshQuota(reservation1, 100); EXPECT_EQ(100, reservation1->remaining_quota()); scoped_refptr<QuotaReservation> reservation2 = reservation_manager()->CreateReservation(origin, type); EXPECT_EQ(0, reservation2->remaining_quota()); RefreshQuota(reservation2, 500); EXPECT_EQ(500, reservation2->remaining_quota()); // Attach a file to both of two reservations. scoped_ptr<OpenFileHandle> open_file1 = reservation1->GetOpenFileHandle(file_path()); scoped_ptr<OpenFileHandle> open_file2 = reservation2->GetOpenFileHandle(file_path()); // Each client should manage reserved quota and its consumption separately. int64 remaining_quota1 = reservation1->remaining_quota(); int64 base_file_size1 = open_file1->base_file_size(); int64 max_written_offset1 = base_file_size1; int64 remaining_quota2 = reservation2->remaining_quota(); int64 base_file_size2 = open_file2->base_file_size(); int64 max_written_offset2 = base_file_size2; max_written_offset1 = 50; remaining_quota1 -= max_written_offset1 - base_file_size1; base_file_size1 = max_written_offset1; ExtendFileTo(max_written_offset1); max_written_offset2 = 400; remaining_quota2 -= max_written_offset2 - base_file_size2; base_file_size2 = max_written_offset2; ExtendFileTo(max_written_offset2); // For multiple Reservation case, RefreshQuota needs usage report only from // associated OpenFile's. base_file_size1 = open_file1->UpdateMaxWrittenOffset(max_written_offset1); max_written_offset1 = base_file_size1; EXPECT_EQ(100 - (50 - kInitialFileSize), reservation1->remaining_quota()); RefreshQuota(reservation1, 200); EXPECT_EQ(200, reservation1->remaining_quota()); base_file_size2 = open_file2->UpdateMaxWrittenOffset(max_written_offset2); max_written_offset2 = base_file_size2; EXPECT_EQ(500 - (400 - 50), reservation2->remaining_quota()); RefreshQuota(reservation2, 150); EXPECT_EQ(150, reservation2->remaining_quota()); open_file1.reset(); open_file2.reset(); EXPECT_EQ(400, GetFileSize()); EXPECT_EQ(200 + 150, fake_backend()->on_memory_usage()); EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); reservation1 = NULL; EXPECT_EQ(400, GetFileSize()); EXPECT_EQ(150, fake_backend()->on_memory_usage()); EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); reservation2 = NULL; EXPECT_EQ(400, GetFileSize()); EXPECT_EQ(0, fake_backend()->on_memory_usage()); EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); } // TODO(tzik): Add Truncate test. // TODO(tzik): Add PluginCrash test and DropReservationManager test. } // namespace fileapi