| // 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/containers/contains.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_locator.h" |
| #include "components/services/storage/public/cpp/buckets/constants.h" |
| #include "sql/database.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 "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" |
| #include "url/gurl.h" |
| |
| using ::blink::StorageKey; |
| |
| namespace storage { |
| |
| namespace { |
| |
| // Declared to shorten the line lengths. |
| static const blink::mojom::StorageType kTemp = |
| blink::mojom::StorageType::kTemporary; |
| static const blink::mojom::StorageType kPerm = |
| blink::mojom::StorageType::kPersistent; |
| |
| bool ContainsBucket(const std::set<BucketLocator>& buckets, |
| const BucketInfo& target_bucket) { |
| BucketLocator target_bucket_locator( |
| target_bucket.id, target_bucket.storage_key, target_bucket.type, |
| target_bucket.name == kDefaultBucketName); |
| auto it = buckets.find(target_bucket_locator); |
| return it != buckets.end(); |
| } |
| |
| } // namespace |
| |
| // Test parameter indicates if the database should be created for incognito |
| // mode. True will create the database in memory. |
| class QuotaDatabaseTest : public testing::TestWithParam<bool> { |
| protected: |
| using QuotaTableEntry = QuotaDatabase::QuotaTableEntry; |
| using BucketTableEntry = QuotaDatabase::BucketTableEntry; |
| using EnsureOpenedMode = QuotaDatabase::EnsureOpenedMode; |
| |
| void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); } |
| |
| void TearDown() override { ASSERT_TRUE(temp_directory_.Delete()); } |
| |
| bool use_in_memory_db() const { return GetParam(); } |
| |
| base::FilePath DbPath() { |
| return temp_directory_.GetPath().AppendASCII("quota_manager.db"); |
| } |
| |
| bool EnsureOpened(QuotaDatabase* db, EnsureOpenedMode mode) { |
| return db->EnsureOpened(mode) == QuotaError::kNone; |
| } |
| |
| 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; |
| } |
| }; |
| |
| QuotaError DumpQuotaTable(QuotaDatabase* quota_database, |
| const QuotaDatabase::QuotaTableCallback& callback) { |
| return quota_database->DumpQuotaTable(callback); |
| } |
| |
| QuotaError DumpBucketTable( |
| QuotaDatabase* quota_database, |
| const QuotaDatabase::BucketTableCallback& callback) { |
| return quota_database->DumpBucketTable(callback); |
| } |
| |
| template <typename Container> |
| void AssignQuotaTable(QuotaDatabase* quota_database, Container&& entries) { |
| ASSERT_NE(quota_database->db_.get(), nullptr); |
| for (const auto& entry : entries) { |
| const char* kSql = |
| // clang-format off |
| "INSERT INTO quota(host, type, quota) " |
| "VALUES (?, ?, ?)"; |
| // clang-format on |
| sql::Statement statement; |
| statement.Assign( |
| quota_database->db_.get()->GetCachedStatement(SQL_FROM_HERE, kSql)); |
| ASSERT_TRUE(statement.is_valid()); |
| |
| statement.BindString(0, entry.host); |
| statement.BindInt(1, static_cast<int>(entry.type)); |
| statement.BindInt64(2, entry.quota); |
| EXPECT_TRUE(statement.Run()); |
| } |
| quota_database->Commit(); |
| } |
| |
| template <typename Container> |
| void AssignBucketTable(QuotaDatabase* quota_database, Container&& entries) { |
| ASSERT_NE(quota_database->db_.get(), (sql::Database*)nullptr); |
| for (const auto& entry : entries) { |
| const char* kSql = |
| // clang-format off |
| "INSERT INTO buckets(" |
| "id," |
| "storage_key," |
| "host," |
| "type," |
| "name," |
| "use_count," |
| "last_accessed," |
| "last_modified," |
| "expiration," |
| "quota) " |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0)"; |
| // clang-format on |
| sql::Statement statement; |
| statement.Assign( |
| quota_database->db_->GetCachedStatement(SQL_FROM_HERE, kSql)); |
| ASSERT_TRUE(statement.is_valid()); |
| |
| statement.BindInt64(0, entry.bucket_id.value()); |
| statement.BindString(1, entry.storage_key.Serialize()); |
| statement.BindString(2, entry.storage_key.origin().host()); |
| statement.BindInt(3, static_cast<int>(entry.type)); |
| statement.BindString(4, entry.name); |
| statement.BindInt(5, entry.use_count); |
| statement.BindTime(6, entry.last_accessed); |
| statement.BindTime(7, entry.last_modified); |
| EXPECT_TRUE(statement.Run()); |
| } |
| quota_database->Commit(); |
| } |
| |
| private: |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| base::ScopedTempDir temp_directory_; |
| }; |
| |
| TEST_P(QuotaDatabaseTest, EnsureOpened) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_FALSE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| if (GetParam()) { |
| // Path should not exist for incognito mode. |
| ASSERT_FALSE(base::PathExists(DbPath())); |
| } else { |
| ASSERT_TRUE(base::PathExists(DbPath())); |
| } |
| } |
| |
| TEST_P(QuotaDatabaseTest, HostQuota) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const char* kHost = "foo.com"; |
| const int kQuota1 = 13579; |
| const int kQuota2 = kQuota1 + 1024; |
| |
| QuotaErrorOr<int64_t> result = db.GetHostQuota(kHost, kTemp); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| result = db.GetHostQuota(kHost, kPerm); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| // Insert quota for temporary. |
| EXPECT_EQ(db.SetHostQuota(kHost, kTemp, kQuota1), QuotaError::kNone); |
| result = db.GetHostQuota(kHost, kTemp); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(kQuota1, result.value()); |
| |
| // Update quota for temporary. |
| EXPECT_EQ(db.SetHostQuota(kHost, kTemp, kQuota2), QuotaError::kNone); |
| result = db.GetHostQuota(kHost, kTemp); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(kQuota2, result.value()); |
| |
| // Quota for persistent must not be updated. |
| result = db.GetHostQuota(kHost, kPerm); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| // Delete temporary storage quota. |
| EXPECT_EQ(db.DeleteHostQuota(kHost, kTemp), QuotaError::kNone); |
| result = db.GetHostQuota(kHost, kTemp); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| // Delete persistent quota by setting it to zero. |
| EXPECT_EQ(db.SetHostQuota(kHost, kPerm, 0), QuotaError::kNone); |
| result = db.GetHostQuota(kHost, kPerm); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetOrCreateBucket) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://google/"); |
| std::string bucket_name = "google_bucket"; |
| |
| QuotaErrorOr<BucketInfo> result = |
| db.GetOrCreateBucket(storage_key, bucket_name); |
| ASSERT_TRUE(result.ok()); |
| |
| BucketInfo created_bucket = result.value(); |
| ASSERT_GT(created_bucket.id.value(), 0); |
| ASSERT_EQ(created_bucket.name, bucket_name); |
| ASSERT_EQ(created_bucket.storage_key, storage_key); |
| ASSERT_EQ(created_bucket.type, kTemp); |
| |
| // Should return the same bucket when querying again. |
| result = db.GetOrCreateBucket(storage_key, bucket_name); |
| ASSERT_TRUE(result.ok()); |
| |
| BucketInfo retrieved_bucket = result.value(); |
| ASSERT_EQ(retrieved_bucket.id, created_bucket.id); |
| ASSERT_EQ(retrieved_bucket.name, created_bucket.name); |
| ASSERT_EQ(retrieved_bucket.storage_key, created_bucket.storage_key); |
| ASSERT_EQ(retrieved_bucket.type, created_bucket.type); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucket) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| // Add a bucket entry into the bucket table. |
| StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://google/"); |
| std::string bucket_name = "google_bucket"; |
| QuotaErrorOr<BucketInfo> result = |
| db.CreateBucketForTesting(storage_key, bucket_name, kPerm); |
| ASSERT_TRUE(result.ok()); |
| |
| BucketInfo created_bucket = result.value(); |
| ASSERT_GT(created_bucket.id.value(), 0); |
| ASSERT_EQ(created_bucket.name, bucket_name); |
| ASSERT_EQ(created_bucket.storage_key, storage_key); |
| ASSERT_EQ(created_bucket.type, kPerm); |
| |
| result = db.GetBucket(storage_key, bucket_name, kPerm); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_EQ(result.value().id, created_bucket.id); |
| EXPECT_EQ(result.value().name, created_bucket.name); |
| EXPECT_EQ(result.value().storage_key, created_bucket.storage_key); |
| ASSERT_EQ(result.value().type, created_bucket.type); |
| |
| // Can't retrieve buckets with name mismatch. |
| result = db.GetBucket(storage_key, "does_not_exist", kPerm); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| // Can't retrieve buckets with StorageKey mismatch. |
| result = |
| db.GetBucket(StorageKey::CreateFromStringForTesting("http://example/"), |
| bucket_name, kPerm); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucketsForType) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key1 = |
| StorageKey::CreateFromStringForTesting("http://example-a/"); |
| const StorageKey storage_key2 = |
| StorageKey::CreateFromStringForTesting("http://example-b/"); |
| const StorageKey storage_key3 = |
| StorageKey::CreateFromStringForTesting("http://example-c/"); |
| |
| QuotaErrorOr<BucketInfo> bucket_result = |
| db.CreateBucketForTesting(storage_key1, "temp_bucket", kTemp); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo temp_bucket1 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key2, "temp_bucket", kTemp); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo temp_bucket2 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key1, "perm_bucket", kPerm); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo perm_bucket1 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key3, "perm_bucket", kPerm); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo perm_bucket2 = bucket_result.value(); |
| |
| QuotaErrorOr<std::set<BucketLocator>> result = db.GetBucketsForType(kTemp); |
| ASSERT_TRUE(result.ok()); |
| std::set<BucketLocator> buckets = result.value(); |
| ASSERT_EQ(2U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, temp_bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, temp_bucket2)); |
| |
| result = db.GetBucketsForType(kPerm); |
| ASSERT_TRUE(result.ok()); |
| buckets = result.value(); |
| ASSERT_EQ(2U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, perm_bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, perm_bucket2)); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucketsForHost) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| QuotaErrorOr<BucketInfo> temp_example_bucket1 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("https://example.com/"), "default", |
| kTemp); |
| QuotaErrorOr<BucketInfo> temp_example_bucket2 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://example.com:123/"), |
| "default", kTemp); |
| QuotaErrorOr<BucketInfo> perm_google_bucket1 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://google.com/"), "default", |
| kPerm); |
| QuotaErrorOr<BucketInfo> temp_google_bucket2 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://google.com:123/"), |
| "default", kTemp); |
| |
| QuotaErrorOr<std::set<BucketLocator>> result = |
| db.GetBucketsForHost("example.com", kTemp); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->size(), 2U); |
| EXPECT_TRUE(ContainsBucket(result.value(), temp_example_bucket1.value())); |
| EXPECT_TRUE(ContainsBucket(result.value(), temp_example_bucket2.value())); |
| |
| result = db.GetBucketsForHost("example.com", kPerm); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->size(), 0U); |
| |
| result = db.GetBucketsForHost("google.com", kPerm); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->size(), 1U); |
| EXPECT_TRUE(ContainsBucket(result.value(), perm_google_bucket1.value())); |
| |
| result = db.GetBucketsForHost("google.com", kTemp); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(result->size(), 1U); |
| EXPECT_TRUE(ContainsBucket(result.value(), temp_google_bucket2.value())); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucketsForStorageKey) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key1 = |
| StorageKey::CreateFromStringForTesting("http://example-a/"); |
| const StorageKey storage_key2 = |
| StorageKey::CreateFromStringForTesting("http://example-b/"); |
| |
| QuotaErrorOr<BucketInfo> bucket_result = |
| db.CreateBucketForTesting(storage_key1, "temp_test1", kTemp); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo temp_bucket1 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key1, "temp_test2", kTemp); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo temp_bucket2 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key1, "perm_test", kPerm); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo perm_bucket1 = bucket_result.value(); |
| |
| bucket_result = db.CreateBucketForTesting(storage_key2, "perm_test", kPerm); |
| ASSERT_TRUE(bucket_result.ok()); |
| BucketInfo perm_bucket2 = bucket_result.value(); |
| |
| QuotaErrorOr<std::set<BucketLocator>> result = |
| db.GetBucketsForStorageKey(storage_key1, kTemp); |
| ASSERT_TRUE(result.ok()); |
| std::set<BucketLocator> buckets = result.value(); |
| ASSERT_EQ(2U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, temp_bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, temp_bucket2)); |
| |
| result = db.GetBucketsForStorageKey(storage_key2, kPerm); |
| ASSERT_TRUE(result.ok()); |
| buckets = result.value(); |
| ASSERT_EQ(1U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, perm_bucket2)); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucketWithNoDb) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_FALSE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); |
| |
| StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://google/"); |
| std::string bucket_name = "google_bucket"; |
| QuotaErrorOr<BucketInfo> result = |
| db.GetBucket(storage_key, bucket_name, kTemp); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| } |
| |
| // TODO(crbug.com/1216094): Update test to have its behavior on Fuchsia/Win |
| // match with other platforms, and enable test on all platforms. |
| #if !defined(OS_FUCHSIA) && !defined(OS_WIN) |
| TEST_F(QuotaDatabaseTest, GetBucketWithOpenDatabaseError) { |
| base::HistogramTester histograms; |
| sql::test::ScopedErrorExpecter expecter; |
| expecter.ExpectError(SQLITE_CANTOPEN); |
| |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| QuotaDatabase db(temp_dir.GetPath()); |
| |
| StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://google/"); |
| std::string bucket_name = "google_bucket"; |
| QuotaErrorOr<BucketInfo> result = |
| db.GetBucket(storage_key, bucket_name, kTemp); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kDatabaseError); |
| |
| EXPECT_TRUE(expecter.SawExpectedErrors()); |
| histograms.ExpectTotalCount("Quota.QuotaDatabaseReset", 1); |
| histograms.ExpectBucketCount("Quota.QuotaDatabaseReset", |
| DatabaseResetReason::kOpenDatabase, 1); |
| } |
| #endif // !defined(OS_FUCHSIA) && !defined(OS_WIN) |
| |
| TEST_P(QuotaDatabaseTest, DeleteStorageKeyInfo) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://example-a/"); |
| QuotaErrorOr<BucketInfo> temp_bucket1 = |
| db.CreateBucketForTesting(storage_key, "temp1", kTemp); |
| QuotaErrorOr<BucketInfo> temp_bucket2 = |
| db.CreateBucketForTesting(storage_key, "temp2", kTemp); |
| QuotaErrorOr<BucketInfo> perm_bucket = |
| db.CreateBucketForTesting(storage_key, "perm", kPerm); |
| |
| db.DeleteStorageKeyInfo(storage_key, kTemp); |
| |
| QuotaErrorOr<BucketInfo> result = |
| db.GetBucket(storage_key, temp_bucket1->name, kTemp); |
| ASSERT_FALSE(result.ok()); |
| ASSERT_EQ(result.error(), QuotaError::kNotFound); |
| |
| result = db.GetBucket(storage_key, temp_bucket2->name, kTemp); |
| ASSERT_FALSE(result.ok()); |
| ASSERT_EQ(result.error(), QuotaError::kNotFound); |
| |
| result = db.GetBucket(storage_key, perm_bucket->name, kPerm); |
| ASSERT_TRUE(result.ok()); |
| |
| db.DeleteStorageKeyInfo(storage_key, kPerm); |
| |
| result = db.GetBucket(storage_key, perm_bucket->name, kPerm); |
| ASSERT_FALSE(result.ok()); |
| ASSERT_EQ(result.error(), QuotaError::kNotFound); |
| } |
| |
| TEST_P(QuotaDatabaseTest, SetStorageKeyLastModifiedTime) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://example/"); |
| base::Time now = base::Time::Now(); |
| |
| // Should create a bucket if one doesn't exist. |
| EXPECT_EQ(db.SetStorageKeyLastModifiedTime(storage_key, kTemp, now), |
| QuotaError::kNone); |
| |
| QuotaErrorOr<BucketInfo> bucket = |
| db.GetBucket(storage_key, kDefaultBucketName, kTemp); |
| EXPECT_TRUE(bucket.ok()); |
| |
| EXPECT_EQ(db.SetStorageKeyLastModifiedTime(storage_key, kTemp, now), |
| QuotaError::kNone); |
| |
| QuotaDatabase::BucketTableEntry info; |
| EXPECT_TRUE(db.GetBucketInfo(bucket->id, &info)); |
| EXPECT_EQ(now, info.last_modified); |
| EXPECT_EQ(0, info.use_count); |
| } |
| |
| TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| std::set<BucketId> bucket_exceptions; |
| QuotaErrorOr<BucketLocator> result = |
| db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| // Insert bucket entries into BucketTable. |
| base::Time now = base::Time::Now(); |
| using Entry = QuotaDatabase::BucketTableEntry; |
| Entry bucket1 = Entry( |
| BucketId(1), StorageKey::CreateFromStringForTesting("http://example-a/"), |
| kTemp, kDefaultBucketName, 99, now, now); |
| Entry bucket2 = Entry( |
| BucketId(2), StorageKey::CreateFromStringForTesting("http://example-b/"), |
| kTemp, kDefaultBucketName, 0, now, now); |
| Entry bucket3 = Entry( |
| BucketId(3), StorageKey::CreateFromStringForTesting("http://example-c/"), |
| kTemp, "bucket_c", 1, now, now); |
| Entry bucket4 = Entry( |
| BucketId(4), StorageKey::CreateFromStringForTesting("http://example-d/"), |
| kPerm, "bucket_d", 5, now, now); |
| Entry kTableEntries[] = {bucket1, bucket2, bucket3, bucket4}; |
| AssignBucketTable(&db, kTableEntries); |
| |
| // Update access time for three temporary storages, and |
| EXPECT_EQ(db.SetBucketLastAccessTime(bucket1.bucket_id, |
| base::Time::FromJavaTime(10)), |
| QuotaError::kNone); |
| EXPECT_EQ(db.SetBucketLastAccessTime(bucket2.bucket_id, |
| base::Time::FromJavaTime(20)), |
| QuotaError::kNone); |
| EXPECT_EQ(db.SetBucketLastAccessTime(bucket3.bucket_id, |
| base::Time::FromJavaTime(30)), |
| QuotaError::kNone); |
| |
| // one persistent. |
| EXPECT_EQ(db.SetBucketLastAccessTime(bucket4.bucket_id, |
| base::Time::FromJavaTime(40)), |
| QuotaError::kNone); |
| |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket1.bucket_id, result.value().id); |
| |
| // Test that unlimited origins are excluded from eviction, but |
| // protected origins are not excluded. |
| auto policy = base::MakeRefCounted<MockSpecialStoragePolicy>(); |
| policy->AddUnlimited(bucket1.storage_key.origin().GetURL()); |
| policy->AddProtected(bucket2.storage_key.origin().GetURL()); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, policy.get()); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket2.bucket_id, result.value().id); |
| |
| // Test that durable origins are excluded from eviction. |
| policy->AddDurable(bucket2.storage_key.origin().GetURL()); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, policy.get()); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket3.bucket_id, result.value().id); |
| |
| // Bucket exceptions exclude specified buckets. |
| bucket_exceptions.insert(bucket1.bucket_id); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket2.bucket_id, result.value().id); |
| |
| bucket_exceptions.insert(bucket2.bucket_id); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket3.bucket_id, result.value().id); |
| |
| bucket_exceptions.insert(bucket3.bucket_id); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| |
| EXPECT_EQ(db.SetBucketLastAccessTime(bucket1.bucket_id, base::Time::Now()), |
| QuotaError::kNone); |
| |
| // Delete storage_key/type last access time information. |
| EXPECT_EQ(db.DeleteBucketInfo(bucket3.bucket_id), QuotaError::kNone); |
| |
| // Querying again to see if the deletion has worked. |
| bucket_exceptions.clear(); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_TRUE(result.ok()); |
| EXPECT_EQ(bucket2.bucket_id, result.value().id); |
| |
| bucket_exceptions.insert(bucket1.bucket_id); |
| bucket_exceptions.insert(bucket2.bucket_id); |
| result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.error(), QuotaError::kNotFound); |
| } |
| |
| TEST_P(QuotaDatabaseTest, SetStorageKeyLastAccessTime) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key = |
| StorageKey::CreateFromStringForTesting("http://example/"); |
| base::Time now = base::Time::Now(); |
| |
| // Should create a bucket if one doesn't exist. |
| EXPECT_EQ(db.SetStorageKeyLastAccessTime(storage_key, kTemp, now), |
| QuotaError::kNone); |
| |
| QuotaErrorOr<BucketInfo> bucket = |
| db.GetBucket(storage_key, kDefaultBucketName, kTemp); |
| EXPECT_TRUE(bucket.ok()); |
| |
| EXPECT_EQ(db.SetStorageKeyLastAccessTime(storage_key, kTemp, now), |
| QuotaError::kNone); |
| |
| QuotaDatabase::BucketTableEntry info; |
| EXPECT_TRUE(db.GetBucketInfo(bucket->id, &info)); |
| EXPECT_EQ(now, info.last_accessed); |
| EXPECT_EQ(2, info.use_count); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetStorageKeysForType) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| const StorageKey storage_key1 = |
| StorageKey::CreateFromStringForTesting("http://example-a/"); |
| const StorageKey storage_key2 = |
| StorageKey::CreateFromStringForTesting("http://example-b/"); |
| const StorageKey storage_key3 = |
| StorageKey::CreateFromStringForTesting("http://example-c/"); |
| |
| db.CreateBucketForTesting(storage_key1, "bucket_a", kTemp); |
| db.CreateBucketForTesting(storage_key2, "bucket_b", kTemp); |
| db.CreateBucketForTesting(storage_key2, "bucket_b", kPerm); |
| db.CreateBucketForTesting(storage_key3, "bucket_c", kPerm); |
| |
| QuotaErrorOr<std::set<StorageKey>> result = db.GetStorageKeysForType(kTemp); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(base::Contains(result.value(), storage_key1)); |
| ASSERT_TRUE(base::Contains(result.value(), storage_key2)); |
| ASSERT_FALSE(base::Contains(result.value(), storage_key3)); |
| |
| result = db.GetStorageKeysForType(kPerm); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_FALSE(base::Contains(result.value(), storage_key1)); |
| ASSERT_TRUE(base::Contains(result.value(), storage_key2)); |
| ASSERT_TRUE(base::Contains(result.value(), storage_key3)); |
| } |
| |
| TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| |
| QuotaErrorOr<std::set<BucketLocator>> result = |
| db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); |
| EXPECT_TRUE(result.ok()); |
| std::set<BucketLocator> buckets = result.value(); |
| EXPECT_TRUE(buckets.empty()); |
| |
| QuotaErrorOr<BucketInfo> result1 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://example-a/"), "bucket_a", |
| kTemp); |
| EXPECT_TRUE(result1.ok()); |
| BucketInfo bucket1 = result1.value(); |
| QuotaErrorOr<BucketInfo> result2 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://example-b/"), "bucket_b", |
| kTemp); |
| EXPECT_TRUE(result2.ok()); |
| BucketInfo bucket2 = result2.value(); |
| QuotaErrorOr<BucketInfo> result3 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://example-c/"), "bucket_c", |
| kTemp); |
| EXPECT_TRUE(result3.ok()); |
| BucketInfo bucket3 = result3.value(); |
| QuotaErrorOr<BucketInfo> result4 = db.CreateBucketForTesting( |
| StorageKey::CreateFromStringForTesting("http://example-d/"), "bucket_d", |
| kPerm); |
| EXPECT_TRUE(result4.ok()); |
| BucketInfo bucket4 = result4.value(); |
| |
| // Report last modified time for the buckets. |
| EXPECT_EQ( |
| db.SetBucketLastModifiedTime(bucket1.id, base::Time::FromJavaTime(0)), |
| QuotaError::kNone); |
| EXPECT_EQ( |
| db.SetBucketLastModifiedTime(bucket2.id, base::Time::FromJavaTime(10)), |
| QuotaError::kNone); |
| EXPECT_EQ( |
| db.SetBucketLastModifiedTime(bucket3.id, base::Time::FromJavaTime(20)), |
| QuotaError::kNone); |
| EXPECT_EQ( |
| db.SetBucketLastModifiedTime(bucket4.id, base::Time::FromJavaTime(30)), |
| QuotaError::kNone); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(3U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket2)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket3)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket4)); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), |
| base::Time::Max()); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(2U, buckets.size()); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket2)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket3)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket4)); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15), |
| base::Time::Max()); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(1U, buckets.size()); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket1)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket2)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket3)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket4)); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25), |
| base::Time::Max()); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_TRUE(buckets.empty()); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), |
| base::Time::FromJavaTime(15)); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(1U, buckets.size()); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket2)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket3)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket4)); |
| |
| result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0), |
| base::Time::FromJavaTime(20)); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(2U, buckets.size()); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket1)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket2)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket3)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket4)); |
| |
| result = db.GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0), |
| base::Time::FromJavaTime(35)); |
| EXPECT_TRUE(result.ok()); |
| buckets = result.value(); |
| EXPECT_EQ(1U, buckets.size()); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket1)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket2)); |
| EXPECT_FALSE(ContainsBucket(buckets, bucket3)); |
| EXPECT_TRUE(ContainsBucket(buckets, bucket4)); |
| } |
| |
| TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) { |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| |
| const StorageKey kStorageKeys[] = { |
| StorageKey::CreateFromStringForTesting("http://a/"), |
| StorageKey::CreateFromStringForTesting("http://b/"), |
| StorageKey::CreateFromStringForTesting("http://c/")}; |
| std::set<StorageKey> storage_keys(kStorageKeys, std::end(kStorageKeys)); |
| |
| EXPECT_TRUE(db.RegisterInitialStorageKeyInfo(storage_keys, kTemp)); |
| |
| QuotaErrorOr<BucketInfo> bucket_result = |
| db.GetBucket(StorageKey::CreateFromStringForTesting("http://a/"), |
| kDefaultBucketName, kTemp); |
| ASSERT_TRUE(bucket_result.ok()); |
| |
| QuotaDatabase::BucketTableEntry info; |
| info.use_count = -1; |
| EXPECT_TRUE(db.GetBucketInfo(bucket_result->id, &info)); |
| EXPECT_EQ(0, info.use_count); |
| |
| EXPECT_EQ(db.SetStorageKeyLastAccessTime( |
| StorageKey::CreateFromStringForTesting("http://a/"), kTemp, |
| base::Time::FromDoubleT(1.0)), |
| QuotaError::kNone); |
| info.use_count = -1; |
| EXPECT_TRUE(db.GetBucketInfo(bucket_result->id, &info)); |
| EXPECT_EQ(1, info.use_count); |
| |
| EXPECT_TRUE(db.RegisterInitialStorageKeyInfo(storage_keys, kTemp)); |
| |
| info.use_count = -1; |
| EXPECT_TRUE(db.GetBucketInfo(bucket_result->id, &info)); |
| EXPECT_EQ(1, info.use_count); |
| } |
| |
| TEST_P(QuotaDatabaseTest, DumpQuotaTable) { |
| QuotaTableEntry kTableEntries[] = { |
| {.host = "http://go/", .type = kTemp, .quota = 1}, |
| {.host = "http://oo/", .type = kTemp, .quota = 2}, |
| {.host = "http://gle/", .type = kPerm, .quota = 3}}; |
| |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| AssignQuotaTable(&db, kTableEntries); |
| |
| using Verifier = EntryVerifier<QuotaTableEntry>; |
| Verifier verifier(kTableEntries, std::end(kTableEntries)); |
| EXPECT_EQ( |
| DumpQuotaTable(&db, base::BindRepeating(&Verifier::Run, |
| base::Unretained(&verifier))), |
| QuotaError::kNone); |
| EXPECT_TRUE(verifier.table.empty()); |
| } |
| |
| TEST_P(QuotaDatabaseTest, DumpBucketTable) { |
| base::Time now = base::Time::Now(); |
| using Entry = QuotaDatabase::BucketTableEntry; |
| Entry kTableEntries[] = { |
| Entry(BucketId(1), StorageKey::CreateFromStringForTesting("http://go/"), |
| kTemp, kDefaultBucketName, 2147483647, now, now), |
| Entry(BucketId(2), StorageKey::CreateFromStringForTesting("http://oo/"), |
| kTemp, kDefaultBucketName, 0, now, now), |
| Entry(BucketId(3), StorageKey::CreateFromStringForTesting("http://gle/"), |
| kTemp, kDefaultBucketName, 1, now, now), |
| }; |
| |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| AssignBucketTable(&db, kTableEntries); |
| |
| using Verifier = EntryVerifier<Entry>; |
| Verifier verifier(kTableEntries, std::end(kTableEntries)); |
| EXPECT_EQ( |
| DumpBucketTable(&db, base::BindRepeating(&Verifier::Run, |
| base::Unretained(&verifier))), |
| QuotaError::kNone); |
| EXPECT_TRUE(verifier.table.empty()); |
| } |
| |
| TEST_P(QuotaDatabaseTest, GetBucketInfo) { |
| using Entry = QuotaDatabase::BucketTableEntry; |
| Entry kTableEntries[] = { |
| Entry(BucketId(123), StorageKey::CreateFromStringForTesting("http://go/"), |
| kTemp, "test_bucket", 100, base::Time(), base::Time())}; |
| |
| QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); |
| EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| AssignBucketTable(&db, kTableEntries); |
| |
| { |
| Entry entry; |
| EXPECT_TRUE(db.GetBucketInfo(kTableEntries[0].bucket_id, &entry)); |
| EXPECT_EQ(kTableEntries[0].bucket_id, entry.bucket_id); |
| EXPECT_EQ(kTableEntries[0].type, entry.type); |
| EXPECT_EQ(kTableEntries[0].storage_key, entry.storage_key); |
| EXPECT_EQ(kTableEntries[0].name, entry.name); |
| EXPECT_EQ(kTableEntries[0].use_count, entry.use_count); |
| EXPECT_EQ(kTableEntries[0].last_accessed, entry.last_accessed); |
| EXPECT_EQ(kTableEntries[0].last_modified, entry.last_modified); |
| } |
| |
| { |
| // BucketId 456 is not in the database. |
| Entry entry; |
| EXPECT_FALSE(db.GetBucketInfo(BucketId(456), &entry)); |
| } |
| } |
| |
| // Non-parameterized tests. |
| TEST_F(QuotaDatabaseTest, BootstrapForEvictionFlag) { |
| QuotaDatabase db(DbPath()); |
| |
| EXPECT_FALSE(db.IsBootstrappedForEviction()); |
| EXPECT_TRUE(db.SetBootstrappedForEviction(true)); |
| EXPECT_TRUE(db.IsBootstrappedForEviction()); |
| EXPECT_TRUE(db.SetBootstrappedForEviction(false)); |
| EXPECT_FALSE(db.IsBootstrappedForEviction()); |
| } |
| |
| TEST_F(QuotaDatabaseTest, OpenCorruptedDatabase) { |
| base::HistogramTester histograms; |
| // Create database, force corruption and close db by leaving scope. |
| { |
| QuotaDatabase db(DbPath()); |
| ASSERT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); |
| ASSERT_TRUE(sql::test::CorruptSizeInHeader(DbPath())); |
| } |
| // Reopen database and verify schema reset on reopen. |
| { |
| sql::test::ScopedErrorExpecter expecter; |
| expecter.ExpectError(SQLITE_CORRUPT); |
| QuotaDatabase db(DbPath()); |
| ASSERT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); |
| EXPECT_TRUE(expecter.SawExpectedErrors()); |
| } |
| |
| histograms.ExpectTotalCount("Quota.QuotaDatabaseReset", 1); |
| histograms.ExpectBucketCount("Quota.QuotaDatabaseReset", |
| DatabaseResetReason::kCreateSchema, 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| QuotaDatabaseTest, |
| /* is_incognito */ testing::Bool()); |
| |
| } // namespace storage |