// 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 "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/file.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" using fileapi::FileSystemType; using fileapi::kFileSystemTypeTemporary; using fileapi::OpenFileHandle; using fileapi::QuotaReservation; using fileapi::QuotaReservationManager; namespace content { namespace { const char kOrigin[] = "http://example.com"; const FileSystemType kType = kFileSystemTypeTemporary; const int64 kInitialFileSize = 1; typedef QuotaReservationManager::ReserveQuotaCallback ReserveQuotaCallback; int64 GetFileSize(const base::FilePath& path) { int64 size = 0; base::GetFileSize(path, &size); return size; } void SetFileSize(const base::FilePath& path, int64 size) { base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE); ASSERT_TRUE(file.IsValid()); ASSERT_TRUE(file.SetLength(size)); } class FakeBackend : public QuotaReservationManager::QuotaBackend { public: FakeBackend() : on_memory_usage_(kInitialFileSize), on_disk_usage_(kInitialFileSize) {} 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::File::FILE_OK, delta)); } 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; on_memory_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); }; class FakeWriter { public: explicit FakeWriter(scoped_ptr handle) : handle_(handle.Pass()), path_(handle_->platform_path()), max_written_offset_(handle_->GetEstimatedFileSize()), append_mode_write_amount_(0), dirty_(false) { } ~FakeWriter() { if (handle_) EXPECT_FALSE(dirty_); } int64 Truncate(int64 length) { int64 consumed = 0; if (max_written_offset_ < length) { consumed = length - max_written_offset_; max_written_offset_ = length; } SetFileSize(path_, length); return consumed; } int64 Write(int64 max_offset) { dirty_ = true; int64 consumed = 0; if (max_written_offset_ < max_offset) { consumed = max_offset - max_written_offset_; max_written_offset_ = max_offset; } if (GetFileSize(path_) < max_offset) SetFileSize(path_, max_offset); return consumed; } int64 Append(int64 amount) { dirty_ = true; append_mode_write_amount_ += amount; SetFileSize(path_, GetFileSize(path_) + amount); return amount; } void ReportUsage() { handle_->UpdateMaxWrittenOffset(max_written_offset_); handle_->AddAppendModeWriteAmount(append_mode_write_amount_); max_written_offset_ = handle_->GetEstimatedFileSize(); append_mode_write_amount_ = 0; dirty_ = false; } void ClearWithoutUsageReport() { handle_.reset(); } private: scoped_ptr handle_; base::FilePath path_; int64 max_written_offset_; int64 append_mode_write_amount_; bool dirty_; }; void ExpectSuccess(bool* done, base::File::Error error) { EXPECT_FALSE(*done); *done = true; EXPECT_EQ(base::File::FILE_OK, error); } void RefreshReservation(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(file_path_, kInitialFileSize); scoped_ptr backend(new FakeBackend); reservation_manager_.reset(new QuotaReservationManager(backend.Pass())); } virtual void TearDown() OVERRIDE { reservation_manager_.reset(); } FakeBackend* fake_backend() { return static_cast(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 reservation_manager_; DISALLOW_COPY_AND_ASSIGN(QuotaReservationManagerTest); }; TEST_F(QuotaReservationManagerTest, BasicTest) { scoped_refptr reservation = reservation_manager()->CreateReservation(GURL(kOrigin), kType); { RefreshReservation(reservation.get(), 10 + 20 + 3); int64 cached_reserved_quota = reservation->remaining_quota(); FakeWriter writer(reservation->GetOpenFileHandle(file_path())); cached_reserved_quota -= writer.Write(kInitialFileSize + 10); EXPECT_LE(0, cached_reserved_quota); cached_reserved_quota -= writer.Append(20); EXPECT_LE(0, cached_reserved_quota); writer.ReportUsage(); } EXPECT_EQ(3, reservation->remaining_quota()); EXPECT_EQ(kInitialFileSize + 10 + 20, GetFileSize(file_path())); EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_disk_usage()); EXPECT_EQ(kInitialFileSize + 10 + 20 + 3, fake_backend()->on_memory_usage()); { RefreshReservation(reservation.get(), 5); FakeWriter writer(reservation->GetOpenFileHandle(file_path())); EXPECT_EQ(0, writer.Truncate(3)); writer.ReportUsage(); } EXPECT_EQ(5, reservation->remaining_quota()); EXPECT_EQ(3, GetFileSize(file_path())); EXPECT_EQ(3, fake_backend()->on_disk_usage()); EXPECT_EQ(3 + 5, fake_backend()->on_memory_usage()); reservation = NULL; EXPECT_EQ(3, fake_backend()->on_memory_usage()); } TEST_F(QuotaReservationManagerTest, MultipleWriter) { scoped_refptr reservation = reservation_manager()->CreateReservation(GURL(kOrigin), kType); { RefreshReservation(reservation.get(), 10 + 20 + 30 + 40 + 5); int64 cached_reserved_quota = reservation->remaining_quota(); FakeWriter writer1(reservation->GetOpenFileHandle(file_path())); FakeWriter writer2(reservation->GetOpenFileHandle(file_path())); FakeWriter writer3(reservation->GetOpenFileHandle(file_path())); cached_reserved_quota -= writer1.Write(kInitialFileSize + 10); EXPECT_LE(0, cached_reserved_quota); cached_reserved_quota -= writer2.Write(kInitialFileSize + 20); cached_reserved_quota -= writer3.Append(30); EXPECT_LE(0, cached_reserved_quota); cached_reserved_quota -= writer3.Append(40); EXPECT_LE(0, cached_reserved_quota); writer1.ReportUsage(); writer2.ReportUsage(); writer3.ReportUsage(); } EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, GetFileSize(file_path())); EXPECT_EQ(kInitialFileSize + 10 + 20 + 30 + 40 + 5, fake_backend()->on_memory_usage()); EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, fake_backend()->on_disk_usage()); reservation = NULL; EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, fake_backend()->on_disk_usage()); } TEST_F(QuotaReservationManagerTest, MultipleClient) { scoped_refptr reservation1 = reservation_manager()->CreateReservation(GURL(kOrigin), kType); RefreshReservation(reservation1, 10); int64 cached_reserved_quota1 = reservation1->remaining_quota(); scoped_refptr reservation2 = reservation_manager()->CreateReservation(GURL(kOrigin), kType); RefreshReservation(reservation2, 20); int64 cached_reserved_quota2 = reservation2->remaining_quota(); scoped_ptr writer1( new FakeWriter(reservation1->GetOpenFileHandle(file_path()))); scoped_ptr writer2( new FakeWriter(reservation2->GetOpenFileHandle(file_path()))); cached_reserved_quota1 -= writer1->Write(kInitialFileSize + 10); EXPECT_LE(0, cached_reserved_quota1); cached_reserved_quota2 -= writer2->Append(20); EXPECT_LE(0, cached_reserved_quota2); writer1->ReportUsage(); RefreshReservation(reservation1.get(), 2); cached_reserved_quota1 = reservation1->remaining_quota(); writer2->ReportUsage(); RefreshReservation(reservation2.get(), 3); cached_reserved_quota2 = reservation2->remaining_quota(); writer1.reset(); writer2.reset(); EXPECT_EQ(kInitialFileSize + 10 + 20, GetFileSize(file_path())); EXPECT_EQ(kInitialFileSize + 10 + 20 + 2 + 3, fake_backend()->on_memory_usage()); EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_disk_usage()); reservation1 = NULL; EXPECT_EQ(kInitialFileSize + 10 + 20 + 3, fake_backend()->on_memory_usage()); reservation2 = NULL; EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_memory_usage()); } TEST_F(QuotaReservationManagerTest, ClientCrash) { scoped_refptr reservation1 = reservation_manager()->CreateReservation(GURL(kOrigin), kType); RefreshReservation(reservation1.get(), 15); scoped_refptr reservation2 = reservation_manager()->CreateReservation(GURL(kOrigin), kType); RefreshReservation(reservation2.get(), 20); { FakeWriter writer(reservation1->GetOpenFileHandle(file_path())); writer.Write(kInitialFileSize + 10); reservation1->OnClientCrash(); writer.ClearWithoutUsageReport(); } reservation1 = NULL; EXPECT_EQ(kInitialFileSize + 10, GetFileSize(file_path())); EXPECT_EQ(kInitialFileSize + 15 + 20, fake_backend()->on_memory_usage()); EXPECT_EQ(kInitialFileSize + 10, fake_backend()->on_disk_usage()); reservation2 = NULL; EXPECT_EQ(kInitialFileSize + 10, fake_backend()->on_memory_usage()); } } // namespace content