// Copyright (c) 2012 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 #include #include #include "base/bind.h" #include "base/callback.h" #include "base/file_util.h" #include "base/message_loop.h" #include "base/scoped_temp_dir.h" #include "googleurl/src/gurl.h" #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/statement.h" #include "sql/transaction.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/quota/mock_special_storage_policy.h" #include "webkit/quota/quota_database.h" namespace quota { namespace { const base::Time kZeroTime; class TestErrorDelegate : public sql::ErrorDelegate { public: virtual int OnError(int error, sql::Connection* connection, sql::Statement* stmt) OVERRIDE { return error; } protected: virtual ~TestErrorDelegate() {} }; } // namespace class QuotaDatabaseTest : public testing::Test { protected: typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry; typedef QuotaDatabase::QuotaTableCallback QuotaTableCallback; typedef QuotaDatabase::OriginInfoTableCallback OriginInfoTableCallback; void LazyOpen(const FilePath& kDbFile) { QuotaDatabase db(kDbFile); EXPECT_FALSE(db.LazyOpen(false)); ASSERT_TRUE(db.LazyOpen(true)); EXPECT_TRUE(db.db_.get()); EXPECT_TRUE(kDbFile.empty() || file_util::PathExists(kDbFile)); } void UpgradeSchemaV2toV3(const FilePath& kDbFile) { const QuotaTableEntry entries[] = { QuotaTableEntry("a", kStorageTypeTemporary, 1), QuotaTableEntry("b", kStorageTypeTemporary, 2), QuotaTableEntry("c", kStorageTypePersistent, 3), }; CreateV2Database(kDbFile, entries, ARRAYSIZE_UNSAFE(entries)); QuotaDatabase db(kDbFile); EXPECT_TRUE(db.LazyOpen(true)); EXPECT_TRUE(db.db_.get()); typedef EntryVerifier Verifier; Verifier verifier(entries, entries + ARRAYSIZE_UNSAFE(entries)); EXPECT_TRUE(db.DumpQuotaTable( new QuotaTableCallback( base::Bind(&Verifier::Run, base::Unretained(&verifier))))); EXPECT_TRUE(verifier.table.empty()); } void HostQuota(const 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 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 FilePath& kDbFile) { QuotaDatabase db(kDbFile); ASSERT_TRUE(db.LazyOpen(true)); const char* kTempQuotaKey = QuotaDatabase::kTemporaryQuotaOverrideKey; const char* kAvailSpaceKey = QuotaDatabase::kDesiredAvailableSpaceKey; int64 value = 0; const int64 kValue1 = 456; const int64 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 FilePath& kDbFile) { QuotaDatabase db(kDbFile); ASSERT_TRUE(db.LazyOpen(true)); std::set 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 policy( new MockSpecialStoragePolicy); policy->AddUnlimited(kOrigin1); policy->AddProtected(kOrigin2); EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, policy, &origin)); EXPECT_EQ(kOrigin2.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 FilePath& kDbFile) { QuotaDatabase db(kDbFile); ASSERT_TRUE(db.LazyOpen(true)); std::set 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 RegisterInitialOriginInfo(const FilePath& kDbFile) { QuotaDatabase db(kDbFile); const GURL kOrigins[] = { GURL("http://a/"), GURL("http://b/"), GURL("http://c/") }; std::set origins(kOrigins, kOrigins + ARRAYSIZE_UNSAFE(kOrigins)); EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary)); int used_count = -1; EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), kStorageTypeTemporary, &used_count)); EXPECT_EQ(0, used_count); EXPECT_TRUE(db.SetOriginLastAccessTime( GURL("http://a/"), kStorageTypeTemporary, base::Time::FromDoubleT(1.0))); used_count = -1; EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), kStorageTypeTemporary, &used_count)); EXPECT_EQ(1, used_count); EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary)); used_count = -1; EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), kStorageTypeTemporary, &used_count)); EXPECT_EQ(1, used_count); } template struct EntryVerifier { std::set table; template EntryVerifier(Iterator itr, Iterator end) : table(itr, end) {} bool Run(const EntryType& entry) { EXPECT_EQ(1u, table.erase(entry)); return true; } }; void DumpQuotaTable(const 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_UNSAFE(kTableEntries); QuotaDatabase db(kDbFile); EXPECT_TRUE(db.LazyOpen(true)); AssignQuotaTable(db.db_.get(), begin, end); db.Commit(); typedef EntryVerifier Verifier; Verifier verifier(begin, end); EXPECT_TRUE(db.DumpQuotaTable( new QuotaTableCallback( base::Bind(&Verifier::Run, base::Unretained(&verifier))))); EXPECT_TRUE(verifier.table.empty()); } void DumpOriginInfoTable(const 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_UNSAFE(kTableEntries); QuotaDatabase db(kDbFile); EXPECT_TRUE(db.LazyOpen(true)); AssignOriginInfoTable(db.db_.get(), begin, end); db.Commit(); typedef EntryVerifier Verifier; Verifier verifier(begin, end); EXPECT_TRUE(db.DumpOriginInfoTable( new OriginInfoTableCallback( base::Bind(&Verifier::Run, base::Unretained(&verifier))))); EXPECT_TRUE(verifier.table.empty()); } private: template 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(itr->type)); statement.BindInt64(2, itr->quota); EXPECT_TRUE(statement.Run()); } } template 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(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 FilePath& kDbFile) { if (kDbFile.empty()) { return db->OpenInMemory(); } if (!file_util::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 FilePath& kDbFile, const QuotaTableEntry* entries, size_t entries_size) { scoped_ptr db(new sql::Connection); scoped_ptr 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_UNSAFE(kTables), kIndexes, ARRAYSIZE_UNSAFE(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(); } MessageLoop message_loop_; }; TEST_F(QuotaDatabaseTest, LazyOpen) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); LazyOpen(kDbFile); LazyOpen(FilePath()); } TEST_F(QuotaDatabaseTest, UpgradeSchema) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); UpgradeSchemaV2toV3(kDbFile); } TEST_F(QuotaDatabaseTest, HostQuota) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); HostQuota(kDbFile); HostQuota(FilePath()); } TEST_F(QuotaDatabaseTest, GlobalQuota) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); GlobalQuota(kDbFile); GlobalQuota(FilePath()); } TEST_F(QuotaDatabaseTest, OriginLastAccessTimeLRU) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); OriginLastAccessTimeLRU(kDbFile); OriginLastAccessTimeLRU(FilePath()); } TEST_F(QuotaDatabaseTest, OriginLastModifiedSince) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); OriginLastModifiedSince(kDbFile); OriginLastModifiedSince(FilePath()); } TEST_F(QuotaDatabaseTest, BootstrapFlag) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); 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) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); RegisterInitialOriginInfo(kDbFile); RegisterInitialOriginInfo(FilePath()); } TEST_F(QuotaDatabaseTest, DumpQuotaTable) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); DumpQuotaTable(kDbFile); DumpQuotaTable(FilePath()); } TEST_F(QuotaDatabaseTest, DumpOriginInfoTable) { ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); DumpOriginInfoTable(kDbFile); DumpOriginInfoTable(FilePath()); } } // namespace quota