| // Copyright 2024 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/persistent_cache/persistent_cache.h" | 
 |  | 
 | #include <cstdint> | 
 | #include <memory> | 
 |  | 
 | #include "base/containers/span.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "components/persistent_cache/backend_params.h" | 
 | #include "components/persistent_cache/mock/mock_backend_impl.h" | 
 | #include "components/persistent_cache/sqlite/sqlite_backend_impl.h" | 
 | #include "components/persistent_cache/sqlite/test_utils.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr const char* kKey = "foo"; | 
 |  | 
 | using testing::_; | 
 | using testing::Return; | 
 |  | 
 | class PersistentCacheMockedBackendTest : public testing::Test { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     backend_ = std::make_unique<persistent_cache::MockBackendImpl>(params_); | 
 |   } | 
 |  | 
 |   void CreateCache(bool successful) { | 
 |     EXPECT_CALL(*backend_, Initialize()).WillOnce(Return(successful)); | 
 |     cache_ = std::make_unique<persistent_cache::PersistentCache>( | 
 |         std::move(backend_)); | 
 |   } | 
 |  | 
 |   persistent_cache::MockBackendImpl* GetBackend() { | 
 |     // Can't be called without a cache. | 
 |     CHECK(cache_); | 
 |     return static_cast<persistent_cache::MockBackendImpl*>( | 
 |         cache_->GetBackendForTesting()); | 
 |   } | 
 |  | 
 |   persistent_cache::BackendParams params_; | 
 |   std::unique_ptr<persistent_cache::MockBackendImpl> backend_; | 
 |   std::unique_ptr<persistent_cache::PersistentCache> cache_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace persistent_cache { | 
 |  | 
 | TEST_F(PersistentCacheMockedBackendTest, CreatingCacheInitializesBackend) { | 
 |   EXPECT_CALL(*backend_, Initialize()).WillOnce(Return(true)); | 
 |  | 
 |   std::unique_ptr<PersistentCache> cache = | 
 |       std::make_unique<PersistentCache>(std::move(backend_)); | 
 |   EXPECT_NE(cache->GetBackendForTesting(), nullptr); | 
 | } | 
 |  | 
 | TEST_F(PersistentCacheMockedBackendTest, CacheFindCallsBackendFind) { | 
 |   CreateCache(true); | 
 |   EXPECT_CALL(*GetBackend(), Find(kKey)).WillOnce(Return(nullptr)); | 
 |   cache_->Find(kKey); | 
 | } | 
 |  | 
 | TEST_F(PersistentCacheMockedBackendTest, CacheInsertCallsBackendInsert) { | 
 |   CreateCache(true); | 
 |   EXPECT_CALL(*GetBackend(), Insert(kKey, _, _)); | 
 |   cache_->Insert(kKey, base::byte_span_from_cstring("1")); | 
 | } | 
 |  | 
 | TEST_F(PersistentCacheMockedBackendTest, | 
 |        FailedBackendInitializationMeansNoFurtherCalls) { | 
 |   EXPECT_CALL(*backend_, Insert(kKey, _, _)).Times(0); | 
 |   EXPECT_CALL(*backend_, Find(kKey)).Times(0); | 
 |  | 
 |   CreateCache(false); | 
 |   cache_->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |   cache_->Find(kKey); | 
 | } | 
 |  | 
 | #if !BUILDFLAG(IS_FUCHSIA) | 
 |  | 
 | class PersistentCacheTest : public testing::Test, | 
 |                             public testing::WithParamInterface<BackendType> { | 
 |  protected: | 
 |   // Used to creates a new cache independent from any other. | 
 |   std::unique_ptr<PersistentCache> OpenCache() { | 
 |     auto cache = PersistentCache::Open( | 
 |         (params_provider_.CreateBackendFilesAndBuildParams(GetParam()))); | 
 |     CHECK(cache->GetBackendForTesting()); | 
 |     return cache; | 
 |   } | 
 |  | 
 |   // Used to create a new cache with provided params. Use with params copied | 
 |   // from the creation of another cache to share backing files between the two. | 
 |   std::unique_ptr<PersistentCache> OpenCache(BackendParams backend_params) { | 
 |     auto cache = PersistentCache::Open(std::move(backend_params)); | 
 |     CHECK(cache->GetBackendForTesting()); | 
 |     return cache; | 
 |   } | 
 |  | 
 |   persistent_cache::test_utils::TestHelper params_provider_; | 
 | }; | 
 |  | 
 | TEST_P(PersistentCacheTest, FindReturnsNullWhenEmpty) { | 
 |   auto cache = OpenCache(); | 
 |   EXPECT_EQ(cache->Find(kKey), nullptr); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, FindReturnsValueWhenPresent) { | 
 |   auto cache = OpenCache(); | 
 |   for (int i = 0; i < 20; ++i) { | 
 |     std::string key = base::NumberToString(i); | 
 |     auto value = base::as_byte_span(key); | 
 |     EXPECT_EQ(cache->Find(key), nullptr); | 
 |     cache->Insert(key, value); | 
 |     std::unique_ptr<Entry> entry = cache->Find(key); | 
 |     ASSERT_NE(entry, nullptr); | 
 |     EXPECT_EQ(entry->GetContentSpan(), value); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, EmptyValueIsStorable) { | 
 |   auto cache = OpenCache(); | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("")); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetContentSpan(), | 
 |             base::span<const std::uint8_t>{}); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, ValueContainingNullCharIsStorable) { | 
 |   auto cache = OpenCache(); | 
 |   constexpr std::array<std::uint8_t, 5> value_array{'\0', 'a', 'b', 'c', '\0'}; | 
 |   const base::span<const std::uint8_t> value_span(value_array); | 
 |   CHECK_EQ(value_span.size(), value_array.size()) | 
 |       << "All characters must be included in span"; | 
 |  | 
 |   cache->Insert(kKey, value_span); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetContentSpan(), value_span); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, ValueContainingInvalidUtf8IsStorable) { | 
 |   auto cache = OpenCache(); | 
 |   constexpr std::array<std::uint8_t, 4> value_array{0x20, 0x0F, 0xFF, 0xFF}; | 
 |   const base::span<const std::uint8_t> value_span(value_array); | 
 |   CHECK( | 
 |       !base::IsStringUTF8(std::string(value_array.begin(), value_array.end()))) | 
 |       << "Test needs invalid utf8"; | 
 |  | 
 |   cache->Insert(kKey, value_span); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetContentSpan(), value_span); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, OverwritingChangesValue) { | 
 |   auto cache = OpenCache(); | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("2")); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetContentSpan(), | 
 |             base::byte_span_from_cstring("2")); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, MetadataIsRetrievable) { | 
 |   EntryMetadata metadata{.input_signature = | 
 |                              base::Time::Now().InMillisecondsSinceUnixEpoch()}; | 
 |  | 
 |   auto cache = OpenCache(); | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("1"), metadata); | 
 |  | 
 |   int64_t seconds_since_epoch = | 
 |       base::Time::Now().InMillisecondsSinceUnixEpoch() / 1000; | 
 |   auto entry = cache->Find(kKey); | 
 |   EXPECT_EQ(entry->GetMetadata().input_signature, metadata.input_signature); | 
 |  | 
 |   EXPECT_GE(entry->GetMetadata().write_timestamp, seconds_since_epoch); | 
 |   // The test is supposed to time out before it takes this long to insert a | 
 |   // value. | 
 |   EXPECT_LE(entry->GetMetadata().write_timestamp, seconds_since_epoch + 30); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, OverwritingChangesMetadata) { | 
 |   EntryMetadata metadata{.input_signature = | 
 |                              base::Time::Now().InMillisecondsSinceUnixEpoch()}; | 
 |  | 
 |   auto cache = OpenCache(); | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("1"), metadata); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetMetadata().input_signature, | 
 |             metadata.input_signature); | 
 |  | 
 |   cache->Insert(kKey, base::byte_span_from_cstring("1"), EntryMetadata{}); | 
 |   EXPECT_EQ(cache->Find(kKey)->GetMetadata().input_signature, 0); | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, MultipleEphemeralCachesAreIndependent) { | 
 |   for (int i = 0; i < 3; ++i) { | 
 |     auto cache = OpenCache(); | 
 |  | 
 |     // `kKey` never inserted in this cache so not found. | 
 |     EXPECT_EQ(cache->Find(kKey), nullptr); | 
 |     cache->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |     // `kKey` now present. | 
 |     EXPECT_NE(cache->Find(kKey), nullptr); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, MultipleLiveCachesAreIndependent) { | 
 |   std::vector<std::unique_ptr<PersistentCache>> caches; | 
 |   for (int i = 0; i < 3; ++i) { | 
 |     caches.push_back(OpenCache()); | 
 |     std::unique_ptr<PersistentCache>& cache = caches.back(); | 
 |  | 
 |     // `kKey` never inserted in this cache so not found. | 
 |     EXPECT_EQ(cache->Find(kKey), nullptr); | 
 |     cache->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |     // `kKey` now present. | 
 |     EXPECT_NE(cache->Find(kKey), nullptr); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, EphemeralCachesSharingParamsShareData) { | 
 |   BackendParams backend_params = | 
 |       params_provider_.CreateBackendFilesAndBuildParams(GetParam()); | 
 |   for (int i = 0; i < 3; ++i) { | 
 |     auto cache = OpenCache(backend_params.Copy()); | 
 |  | 
 |     // First run, setup. | 
 |     if (i == 0) { | 
 |       // `kKey` never inserted so not found. | 
 |       EXPECT_EQ(cache->Find(kKey), nullptr); | 
 |       cache->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |       // `kKey` now present. | 
 |       EXPECT_NE(cache->Find(kKey), nullptr); | 
 |     } else { | 
 |       // `kKey` is present because data is shared. | 
 |       EXPECT_NE(cache->Find(kKey), nullptr); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(PersistentCacheTest, LiveCachesSharingParamsShareData) { | 
 |   BackendParams backend_params = | 
 |       params_provider_.CreateBackendFilesAndBuildParams(GetParam()); | 
 |   std::vector<std::unique_ptr<PersistentCache>> caches; | 
 |  | 
 |   for (int i = 0; i < 3; ++i) { | 
 |     caches.push_back(OpenCache(backend_params.Copy())); | 
 |     std::unique_ptr<PersistentCache>& cache = caches.back(); | 
 |  | 
 |     // First run, setup. | 
 |     if (i == 0) { | 
 |       // `kKey` never inserted so not found. | 
 |       EXPECT_EQ(cache->Find(kKey), nullptr); | 
 |       cache->Insert(kKey, base::byte_span_from_cstring("1")); | 
 |       // `kKey` now present. | 
 |       EXPECT_NE(cache->Find(kKey), nullptr); | 
 |     } else { | 
 |       // `kKey` is present because data is shared. | 
 |       EXPECT_NE(cache->Find(kKey), nullptr); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          PersistentCacheTest, | 
 |                          testing::Values(BackendType::kSqlite)); | 
 | #endif | 
 |  | 
 | }  // namespace persistent_cache |