// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/gpu_persistent_cache.h"

#include "base/barrier_closure.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory_coordinator/utils.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/tracing/trace_test_utils.h"
#include "base/trace_event/trace_config.h"
#include "base/trace_event/trace_log.h"
#include "components/persistent_cache/backend_storage.h"
#include "components/persistent_cache/backend_type.h"
#include "components/persistent_cache/client.h"
#include "components/persistent_cache/pending_backend.h"
#include "gpu/command_buffer/service/memory_cache.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {

namespace {
static constexpr size_t kDefaultMemoryCacheSizeForTesting = 1 << 16;
}

class GpuPersistentCacheTest : public testing::Test {
 public:
  void SetUp() override {
    memory_cache_ = MakeDefaultMemoryCache();
    cache_ = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache_);
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    backend_storage_.emplace(persistent_cache::Client::kTest,
                             persistent_cache::BackendType::kSqlite,
                             temp_dir_.GetPath());
  }

 protected:
  static scoped_refptr<MemoryCache> MakeDefaultMemoryCache() {
    return base::MakeRefCounted<MemoryCache>(kDefaultMemoryCacheSizeForTesting);
  }

  void InitializeCache() {
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("test")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache_->InitializeCache(std::move(pending_backend));
  }

  void RunStoreAndLoadDataMultiThreaded(int num_threads);

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::ScopedTempDir temp_dir_;
  std::optional<persistent_cache::BackendStorage> backend_storage_;
  scoped_refptr<GpuPersistentCache> cache_;
  scoped_refptr<MemoryCache> memory_cache_;
};

TEST_F(GpuPersistentCacheTest,
       StoreAndLoadDataBeforeInitializeWithNoMemoryCache) {
  // Don't initialize cache.
  auto cache_with_no_memory_cache =
      base::MakeRefCounted<GpuPersistentCache>("Test", nullptr);
  const std::string key = "my_key";
  const std::string value = "my_value";

  // StoreData() won't do anything but also won't crash.
  cache_with_no_memory_cache->StoreData(key, base::as_byte_span(value));

  // LoadData() will return zero size since there is no cache yet.
  EXPECT_EQ(cache_with_no_memory_cache->FindKey(key), 0u);
}

// Tests basic store and load functionality on a single thread.
TEST_F(GpuPersistentCacheTest, StoreAndLoadData) {
  InitializeCache();

  const std::string key = "my_key";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  std::vector<uint8_t> buffer(value.size());
  size_t loaded_size = cache_->LoadData(key, buffer);

  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
}

// Tests basic load and store using the Skia, ANGLE and Dawn caching interfaces.
TEST_F(GpuPersistentCacheTest, StoreAndLoadDataMixedInterfaces) {
  InitializeCache();

  // Insert 3 key/value pairs with the 3 caching interfaces.
  const std::string key_dawn = "my_key_dawn";
  const std::string value_dawn = "my_value_dawn";
  cache_->StoreData(key_dawn, base::as_byte_span(value_dawn));

  const std::string key_gr = "my_key_gr";
  sk_sp<SkData> key_gr_data =
      SkData::MakeWithoutCopy(key_gr.c_str(), key_gr.size());
  const std::string value_gr = "my_value_gr";
  sk_sp<SkData> value_gr_data =
      SkData::MakeWithoutCopy(value_gr.c_str(), value_gr.size());
  cache_->store(*key_gr_data, *value_gr_data);

  const std::string key_gl = "my_key_gl";
  const std::string value_gl = "my_value_gl";
  cache_->GLBlobCacheSet(key_gl.c_str(), static_cast<int64_t>(key_gl.size()),
                         value_gl.c_str(),
                         static_cast<int64_t>(value_gl.size()));

  // Load with dawn::Platform::CachingInterface
  auto test_load_dawn = [this](const std::string& key,
                               const std::string& value) {
    std::vector<uint8_t> buffer(value.size());
    size_t loaded_size = cache_->LoadData(key, buffer);

    EXPECT_EQ(loaded_size, value.size());
    EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
  };
  test_load_dawn(key_dawn, value_dawn);
  test_load_dawn(key_gr, value_gr);
  test_load_dawn(key_gl, value_gl);

  // Load with GrContextOptions::PersistentCache
  auto test_load_gr = [this](const std::string& key, const std::string& value) {
    sk_sp<SkData> key_data = SkData::MakeWithoutCopy(key.c_str(), key.size());
    sk_sp buffer = cache_->load(*key_data);

    EXPECT_EQ(buffer->size(), value.size());
    EXPECT_EQ(
        std::string(static_cast<const char*>(buffer->data()), buffer->size()),
        value);
  };
  test_load_gr(key_dawn, value_dawn);
  test_load_gr(key_gr, value_gr);
  test_load_gr(key_gl, value_gl);

  // Load with GL_ANGLE_blob_cache
  auto test_load_gl = [this](const std::string& key, const std::string& value) {
    std::vector<char> buffer(value.size());
    int64_t loaded_size = cache_->GLBlobCacheGet(key.c_str(), key.size(),
                                                 buffer.data(), buffer.size());

    EXPECT_EQ(loaded_size, static_cast<int64_t>(value.size()));
    EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
  };
  test_load_gl(key_dawn, value_dawn);
  test_load_gl(key_gr, value_gr);
  test_load_gl(key_gl, value_gl);
}

// Tests that loading a non-existent key returns 0.
TEST_F(GpuPersistentCacheTest, LoadNonExistentKey) {
  InitializeCache();

  const std::string key = "non_existent_key";
  std::vector<uint8_t> buffer(16);
  size_t loaded_size = cache_->LoadData(key, buffer);
  EXPECT_EQ(loaded_size, 0u);
}

void GpuPersistentCacheTest::RunStoreAndLoadDataMultiThreaded(int num_threads) {
  constexpr int kNumOperationsPerThread = 2;

  base::RunLoop run_loop;
  auto barrier = base::BarrierClosure(num_threads, run_loop.QuitClosure());

  // Post tasks to multiple threads to store and immediately load data.
  for (int i = 0; i < num_threads; ++i) {
    base::ThreadPool::PostTask(
        FROM_HERE, {base::MayBlock()},
        base::BindOnce(
            [](scoped_refptr<GpuPersistentCache> cache, int thread_id,
               base::OnceClosure done_closure) {
              for (int j = 0; j < kNumOperationsPerThread; ++j) {
                std::string key = "key_" + base::NumberToString(thread_id) +
                                  "_" + base::NumberToString(j);
                std::string value = "value_" + base::NumberToString(thread_id) +
                                    "_" + base::NumberToString(j);

                cache->StoreData(key, base::as_byte_span(value));

                std::vector<uint8_t> buffer(value.size());
                size_t loaded_size = cache->LoadData(key, buffer);
                ASSERT_EQ(loaded_size, value.size());
                ASSERT_EQ(std::string(buffer.begin(), buffer.end()), value);
              }
              std::move(done_closure).Run();
            },
            cache_, i, barrier));
  }

  // Wait for all threads to complete.
  run_loop.Run();

  // After all threads are done, verify from the main thread that all data is
  // still present and correct. This ensures that writes from different threads
  // did not corrupt each other's data.
  for (int i = 0; i < num_threads; ++i) {
    for (int j = 0; j < kNumOperationsPerThread; ++j) {
      std::string key =
          "key_" + base::NumberToString(i) + "_" + base::NumberToString(j);
      std::string value =
          "value_" + base::NumberToString(i) + "_" + base::NumberToString(j);
      std::vector<uint8_t> buffer(value.size());
      size_t loaded_size = cache_->LoadData(key, buffer);
      EXPECT_EQ(loaded_size, value.size());
      EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
    }
  }
}

// Tests that storing Dawn/Vulkan monolithic VkPipelineCache data doesn't store
// anything in the memory cache.
TEST_F(GpuPersistentCacheTest, StoreVkPersistentCache) {
  InitializeCache();

  const std::string key = "my_keyMonolithicVkPipelineCache";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  EXPECT_FALSE(memory_cache_->Find(key));
}

// Tests that the cache can be safely written to and read from by multiple
// threads concurrently.
TEST_F(GpuPersistentCacheTest, StoreAndLoadDataMultiThreaded) {
  InitializeCache();

  RunStoreAndLoadDataMultiThreaded(8);
}

// Some internal sql code especially tracings checks that they are called on a
// correct sequence. This test verifies that we can use the cache on multiple
// threads without violating sequence checkers. There is no need to stress test
// with many threads like the above StoreAndLoadDataMultiThreaded. A minimal
// number of threads should suffice.
TEST_F(GpuPersistentCacheTest, StoreAndLoadDataMultiThreadedWithSqlTrace) {
  InitializeCache();

  base::test::TracingEnvironment tracing_environment;
  base::trace_event::TraceLog::GetInstance()->SetEnabled(
      base::trace_event::TraceConfig("sql", ""));

  RunStoreAndLoadDataMultiThreaded(3);

  base::trace_event::TraceLog::GetInstance()->SetDisabled();
}

class GpuPersistentCacheAsyncTest : public GpuPersistentCacheTest {
 protected:
  scoped_refptr<GpuPersistentCache> OpenAsyncCache(
      size_t max_pending_bytes_to_write = std::numeric_limits<size_t>::max()) {
    auto pending_backend = backend_storage_->MakePendingBackend(
        base::FilePath(FILE_PATH_LITERAL("test")),
        /*single_connection=*/true, /*journal_mode_wal=*/true);
    if (!pending_backend.has_value()) {
      ADD_FAILURE() << "Failed to make pending backend for test cache";
      return nullptr;
    }

    GpuPersistentCache::AsyncDiskWriteOpts options;
    options.task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
    options.max_pending_bytes_to_write = max_pending_bytes_to_write;
    auto async_cache = base::MakeRefCounted<GpuPersistentCache>(
        "TestAsync", MakeDefaultMemoryCache(),
        GpuPersistentCache::MetadataOpts{}, std::move(options));
    async_cache->InitializeCache(*std::move(pending_backend));
    return async_cache;
  }
};

// Tests that when an async task runner is used, the data is written to disk
// after a delay. It also verifies that the underlying DiskCache is kept alive
// by the posted task, even after the GpuPersistentCache instance is destroyed.
TEST_F(GpuPersistentCacheAsyncTest, StoreAndLoadDataAsync) {
  const std::string key = "my_key";
  const std::string value = "my_value";

  scoped_refptr<GpuPersistentCache> async_cache = OpenAsyncCache();

  base::HistogramTester histogram_tester;

  // Store data. This will be a delayed write.
  async_cache->StoreData(key, base::as_byte_span(value));

  // No writes should have taken place yet.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 0);

  // Destroy the cache. The posted write task will keep the underlying
  // DiskCache alive until it has run.
  async_cache.reset();

  // Fast forward time to trigger the write.
  task_environment_.FastForwardBy(base::Seconds(2));

  // Now the store should have taken place.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 1);

  // And the data should be in the cache.
  async_cache = OpenAsyncCache();
  auto buffer = base::HeapArray<uint8_t>::Uninit(value.size());
  size_t loaded_size = async_cache->LoadData(key, buffer);
  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
}

// Tests the idle-rescheduling logic. If another cache operation occurs during
// the delay period, the write task should be postponed.
TEST_F(GpuPersistentCacheAsyncTest, StoreAndLoadDataAsync_IdleReschedule) {
  const std::string key = "my_key";
  const std::string value = "my_value";

  scoped_refptr<GpuPersistentCache> async_cache = OpenAsyncCache();

  base::HistogramTester histogram_tester;

  // Store data. This will be a delayed write.
  async_cache->StoreData(key, base::as_byte_span(value));

  // Fast forward a bit, but less than the delay.
  task_environment_.FastForwardBy(base::Milliseconds(500));

  // Perform another operation to reset the idle timer.
  std::vector<uint8_t> dummy_buffer(1);
  async_cache->LoadData("some_other_key", dummy_buffer);

  // Fast forward past the original delay time.
  task_environment_.FastForwardBy(base::Seconds(1));

  // The write should not have happened yet because it was rescheduled.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 0);

  // Fast forward again to let the rescheduled write complete.
  task_environment_.FastForwardBy(base::Seconds(1));

  // The write should now have happened.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 1);

  // Destroy the cache.
  async_cache.reset();

  // And the data should be there.
  async_cache = OpenAsyncCache();
  auto buffer = base::HeapArray<uint8_t>::Uninit(value.size());
  size_t loaded_size = async_cache->LoadData(key, buffer);
  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
}

// Tests that if pending bytes exceed the limit, the write happens after the
// first delay without being rescheduled.
TEST_F(GpuPersistentCacheAsyncTest,
       StoreAndLoadDataAsync_ExceedMaxPendingBytes) {
  // Create the cache with a pending byte limit.
  scoped_refptr<GpuPersistentCache> async_cache =
      OpenAsyncCache(/*max_pending_bytes_to_write=*/10);

  const std::string key = "my_key";
  const std::string value = "my_value_is_longer_than_10";
  ASSERT_GT(key.size() + value.size(), 10u);

  base::HistogramTester histogram_tester;

  // Store data. This will be a delayed write.
  async_cache->StoreData(key, base::as_byte_span(value));

  // Fast forward a bit, but less than the delay.
  task_environment_.FastForwardBy(base::Milliseconds(500));

  // Perform another operation to reset the idle timer. This is to ensure that
  // the write is triggered by the pending bytes limit and not the idle timeout.
  std::vector<uint8_t> dummy_buffer(1);
  async_cache->LoadData("some_other_key", dummy_buffer);

  // The write should not have happened yet.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 0);

  // Fast forward past the delay. The write should have happened because of the
  // pending bytes limit, even though the cache was not idle.
  task_environment_.FastForwardBy(base::Seconds(1));

  // The write should have happened.
  histogram_tester.ExpectTotalCount("GPU.PersistentCache.TestAsync.Store", 1);

  // Destroy the cache.
  async_cache.reset();

  // Verify that the data was written by reopening and reading from the cache.
  async_cache = OpenAsyncCache();
  auto buffer = base::HeapArray<uint8_t>::Uninit(value.size());
  size_t loaded_size = async_cache->LoadData(key, buffer);
  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
}

// Tests that LoadData returns 0 if the provided buffer is smaller than the
// data.
TEST_F(GpuPersistentCacheTest, LoadToPartialBuffer) {
  const std::string key = "my_key";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  std::vector<uint8_t> buffer(value.size() / 2);
  size_t loaded_size = cache_->LoadData(key, buffer);

  EXPECT_EQ(loaded_size, 0u);
}

// Tests that LoadData returns the data size if the provided buffer is larger
// than the data.
TEST_F(GpuPersistentCacheTest, LoadToLargerBuffer) {
  const std::string key = "my_key";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  std::vector<uint8_t> buffer(value.size() * 2);
  size_t loaded_size = cache_->LoadData(key, buffer);

  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.begin() + loaded_size), value);
}

// Tests that GLBlobCacheGet returns 0 if the provided buffer is smaller than
// the data.
TEST_F(GpuPersistentCacheTest, GLBlobCacheGetReadPartialBuffer) {
  const std::string key = "my_key";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  std::vector<char> buffer(value.size() / 2);
  int64_t loaded_size = cache_->GLBlobCacheGet(key.c_str(), key.size(),
                                               buffer.data(), buffer.size());

  EXPECT_EQ(loaded_size, 0);
}

// Test that the persistent cache uses the memory backing if no database files
// are set
TEST_F(GpuPersistentCacheTest, MemoryBackingOnly) {
  const std::string key = "my_key";
  const std::string value = "my_value";
  cache_->StoreData(key, base::as_byte_span(value));

  // Check that the entry exists in the cache.
  std::vector<uint8_t> buffer(value.size());
  size_t loaded_size = cache_->LoadData(key, buffer);

  EXPECT_EQ(loaded_size, value.size());
  EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
}

// Test that the persistent cache uses the memory backing before the database
// files are initialized
TEST_F(GpuPersistentCacheTest, MemoryBackingSyncedToDisk) {
  const std::string key = "my_key";
  const std::string value = "my_value";

  {
    // Store the data to the cache without initializing the database files
    auto cache = base::MakeRefCounted<GpuPersistentCache>(
        "Test", MakeDefaultMemoryCache(), GpuPersistentCache::MetadataOpts{});
    cache->StoreData(key, base::as_byte_span(value));

    // Initialize the cache, the memory storage will be written to disk.
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));

    cache->InitializeCache(std::move(pending_backend));
  }

  // Reload the same persistent cache from disk
  {
    auto cache = base::MakeRefCounted<GpuPersistentCache>(
        "Test", MakeDefaultMemoryCache());
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache->InitializeCache(std::move(pending_backend));

    // Check that the entry exists in the cache.
    std::vector<uint8_t> buffer(value.size());
    size_t loaded_size = cache->LoadData(key, buffer);

    EXPECT_EQ(loaded_size, value.size());
    EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
  }
}

// Verifies that data stored in a persistent cache can be loaded back.
TEST_F(GpuPersistentCacheTest, ReOpenCacheFromFile) {
  const std::string key = "my_key";
  const std::string value = "my_value";

  // Store data to the persistent cache via store interface.
  {
    scoped_refptr<MemoryCache> memory_cache =
        base::MakeRefCounted<MemoryCache>(1024);
    auto cache = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache);
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("ReOpenCacheFromFile")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache->InitializeCache(std::move(pending_backend));

    cache->StoreData(key, base::as_byte_span(value));

    // Check that the entry exists in the memory cache.
    auto memory_entry = memory_cache->Find(key);
    EXPECT_NE(nullptr, memory_entry);
    EXPECT_EQ(value.size(), memory_entry->DataSize());
    EXPECT_EQ(
        std::string(memory_entry->Data().begin(), memory_entry->Data().end()),
        value);

    // Check that the entry exists in the persistent cache.
    std::vector<uint8_t> buffer(value.size());
    size_t loaded_size = cache->LoadData(key, buffer);

    EXPECT_EQ(loaded_size, value.size());
    EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
  }

  // Reload the same persistent cache from disk
  {
    scoped_refptr<MemoryCache> memory_cache =
        base::MakeRefCounted<MemoryCache>(1024);
    auto cache = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache);
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("ReOpenCacheFromFile")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache->InitializeCache(std::move(pending_backend));

    // Check that the entry exists in the persistent cache.
    std::vector<uint8_t> buffer(value.size());
    size_t loaded_size = cache->LoadData(key, buffer);

    EXPECT_EQ(loaded_size, value.size());
    EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);

    // Check that the memory cache now contains the same entry after the
    // LoadData() call above
    auto memory_entry = memory_cache->Find(key);
    EXPECT_NE(nullptr, memory_entry);
    EXPECT_EQ(value.size(), memory_entry->DataSize());
    EXPECT_EQ(
        std::string(memory_entry->Data().begin(), memory_entry->Data().end()),
        value);
  }
}

TEST_F(GpuPersistentCacheTest, MetadataFirstEntriesLoadedToMemory) {
  constexpr size_t key_count = 3;
  const std::array<std::string, key_count> keys = {
      "my_key0",
      "my_key1",
      "my_key2",
  };
  const std::string value = "my_value";

  // Enable metadata with a preload count of all the keys minus one to confirm
  // that the correct amount of entries are preloaded
  GpuPersistentCache::MetadataOpts metadata_enabled;
  metadata_enabled.enabled = true;
  metadata_enabled.preload_count = key_count - 1;

  {
    // Store the data to the cache without initializing the database files
    auto cache = base::MakeRefCounted<GpuPersistentCache>(
        "Test", MakeDefaultMemoryCache(), metadata_enabled);
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache->InitializeCache(std::move(pending_backend));

    // Attempt to load the element from the cache. This will add it to the
    // metadata of first read entries.
    std::vector<uint8_t> buffer(value.size());
    for (size_t i = 0; i < key_count; i++) {
      size_t loaded_size = cache->LoadData(keys[i], buffer);
      EXPECT_EQ(0u, loaded_size) << i;

      // Store the entry so it's in the disk cache.
      cache->StoreData(keys[i], base::as_byte_span(value));
    }

    // Make sure the metadata is flushed.
    cache->FlushMetadataForTesting();
  }

  // Reload the same persistent cache from disk
  {
    auto memory_cache = MakeDefaultMemoryCache();
    auto cache = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache,
                                                          metadata_enabled);
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage_->MakePendingBackend(
            base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
            /*single_connection=*/true, /*journal_mode_wal=*/true));
    cache->InitializeCache(std::move(pending_backend));

    // The Metadata used to preload the one entry into the memory cache.
    for (size_t i = 0; i < key_count; i++) {
      auto entry = memory_cache->Find(keys[i]);
      if (i < key_count - 1) {
        EXPECT_NE(nullptr, entry) << i;
      } else {
        EXPECT_EQ(nullptr, entry) << i;
      }
    }
  }
}

// Verifies that PurgeMemory purges the in-memory cache.
TEST_F(GpuPersistentCacheTest, PurgeMemory) {
  const std::string key = "my_key";
  const std::string value = "my_value";

  cache_->StoreData(key, base::as_byte_span(value));
  EXPECT_NE(nullptr, memory_cache_->Find(key));
  EXPECT_EQ(value.size(), cache_->FindKey(key));

  cache_->PurgeMemory(base::kCriticalMemoryPressureThreshold);

  EXPECT_EQ(nullptr, memory_cache_->Find(key));
  EXPECT_EQ(0u, cache_->FindKey(key));
}

// Verifies that GpuPersistentCacheCollection::PurgeMemory purges all managed
// caches.
TEST_F(GpuPersistentCacheTest, CollectionPurgeMemory) {
  GpuPersistentCacheCollection collection(
      1024, GpuPersistentCache::MetadataOpts(),
      GpuPersistentCache::AsyncDiskWriteOpts());
  auto cache_graphite = collection.GetCache(
      gpu::GpuDiskCacheHandle(kGraphiteDawnGpuDiskCacheHandle));
  auto cache_gr_shader =
      collection.GetCache(gpu::GpuDiskCacheHandle(kGrShaderGpuDiskCacheHandle));

  const std::string key_graphite = "my_key_graphite";
  const std::string value_graphite = "my_value_graphite";
  const std::string key_gr_shader = "my_key_gr_shader";
  const std::string value_gr_shader = "my_value_gr_shader";

  cache_graphite->StoreData(key_graphite, base::as_byte_span(value_graphite));
  cache_gr_shader->StoreData(key_gr_shader,
                             base::as_byte_span(value_gr_shader));

  EXPECT_EQ(value_graphite.size(), cache_graphite->FindKey(key_graphite));
  EXPECT_EQ(value_gr_shader.size(), cache_gr_shader->FindKey(key_gr_shader));

  collection.PurgeMemory(base::kCriticalMemoryPressureThreshold);

  EXPECT_EQ(0u, cache_graphite->FindKey(key_graphite));
  EXPECT_EQ(0u, cache_gr_shader->FindKey(key_gr_shader));
}

// Test fixture which is friends with the CacheMetadata internal class in
// GpuPersistentCache
class GpuPersistentCacheMetadataTest : public testing::Test {};

// Test serialization and deserialization functions with various edge-case keys
TEST_F(GpuPersistentCacheMetadataTest, SerializeDeserialize) {
  GpuPersistentCache::CacheMetadata source_data;
  source_data.first_loaded_keys = {
      "a",
      "",
      std::string("\0a", 2),
      std::string("\0\0", 2),
      std::string(2048, 'a'),
  };

  base::HeapArray<uint8_t> serialized_data = source_data.Serialize();

  GpuPersistentCache::CacheMetadata deserialized_data =
      GpuPersistentCache::CacheMetadata::Deserialize(serialized_data);
  EXPECT_EQ(source_data.first_loaded_keys, deserialized_data.first_loaded_keys);
}

}  // namespace gpu
