910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/code_cache/generated_code_cache.h"

#include <memory>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "content/common/features.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "net/base/network_isolation_key.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {

class GeneratedCodeCacheTest : public testing::TestWithParam<bool> {
 public:
  // This should be larger than |kSmallDataLimit| in generated_code_cache.cc.
  static const size_t kLargeSizeInBytes = 8192;
  // This should be larger than |kLargeDataLimit| in generated_code_cache.cc.
  // Additionally, this shouldn't exceed 1/8 of the maximum cache size below,
  // |kMaxSizeInBytes|.
  static const size_t kVeryLargeSizeInBytes = 128 * 1024;
  static const size_t kMaxSizeInBytes = 1024 * 1024;
  static_assert(kMaxSizeInBytes / kVeryLargeSizeInBytes > 0UL,
                "Cache will be too small to hold a very large item");
  static constexpr char kInitialUrl[] = "http://example.com/script.js";
  static constexpr char kInitialOrigin[] = "http://example.com";
  static constexpr char kInitialData[] = "InitialData";

  GeneratedCodeCacheTest() {
    scoped_feature_list_.InitWithFeatureState(features::kInMemoryCodeCache,
                                              GetParam());
  }

  void SetUp() override {
    ASSERT_TRUE(cache_dir_.CreateUniqueTempDir());
    cache_path_ = cache_dir_.GetPath();
  }

  void TearDown() override {
    disk_cache::FlushCacheThreadForTesting();
    backend_ = nullptr;
    generated_code_cache_.reset();
    task_environment_.RunUntilIdle();
    EXPECT_TRUE(cache_dir_.Delete()) << cache_dir_.GetPath();
  }

  // This function initializes the cache and waits till the transaction is
  // finished. When this function returns, the backend is already initialized.
  void InitializeCache(GeneratedCodeCache::CodeCacheType cache_type,
                       const char* initial_url = kInitialUrl,
                       const char* initial_origin = kInitialOrigin) {
    // Create code cache
    generated_code_cache_ = std::make_unique<GeneratedCodeCache>(
        cache_path_, kMaxSizeInBytes, cache_type);

    GeneratedCodeCache::GetBackendCallback callback = base::BindOnce(
        &GeneratedCodeCacheTest::GetBackendCallback, base::Unretained(this));
    generated_code_cache_->GetBackend(std::move(callback));

    WriteToCache(GURL(initial_url), GURL(initial_origin), kInitialData,
                 base::Time::Now());
    task_environment_.RunUntilIdle();
  }

  // This function initializes the cache and reopens it. When this function
  // returns, the backend initialization is not complete yet. This is used
  // to test the pending operaions path.
  void InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType cache_type) {
    InitializeCache(cache_type);
    backend_ = nullptr;
    generated_code_cache_ = std::make_unique<GeneratedCodeCache>(
        cache_path_, kMaxSizeInBytes, cache_type);
  }

  void WriteToCache(const GURL& url,
                    const GURL& origin_lock,
                    const std::string& data,
                    base::Time response_time) {
    generated_code_cache_->WriteEntry(url, origin_lock,
                                      net::NetworkIsolationKey(), response_time,
                                      base::as_byte_span(data));
  }

  void DeleteFromCache(const GURL& url, const GURL& origin_lock) {
    generated_code_cache_->DeleteEntry(url, origin_lock,
                                       net::NetworkIsolationKey());
  }

  void FetchFromCache(const GURL& url, const GURL& origin_lock) {
    received_ = false;
    GeneratedCodeCache::ReadDataCallback callback = base::BindOnce(
        &GeneratedCodeCacheTest::FetchEntryCallback, base::Unretained(this));
    generated_code_cache_->FetchEntry(
        url, origin_lock, net::NetworkIsolationKey(), std::move(callback));
  }

  void DoomAll() {
    net::CompletionOnceCallback callback = base::BindOnce(
        &GeneratedCodeCacheTest::DoomAllCallback, base::Unretained(this));
    backend_->DoomAllEntries(std::move(callback));
  }

  void GetBackendCallback(disk_cache::Backend* backend) { backend_ = backend; }

  void DoomAllCallback(int rv) {}

  void FetchEntryCallback(const base::Time& response_time,
                          mojo_base::BigBuffer data) {
    if (data.size() == 0) {
      received_ = true;
      received_null_ = true;
      received_response_time_ = response_time;
      return;
    }
    received_ = true;
    received_null_ = false;
    received_data_ = base::as_string_view(base::span(data));
    received_response_time_ = response_time;
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
  base::ScopedTempDir cache_dir_;
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<GeneratedCodeCache> generated_code_cache_;
  std::string received_data_;
  base::Time received_response_time_;
  bool received_;
  bool received_null_;
  base::FilePath cache_path_;
  raw_ptr<disk_cache::Backend> backend_ = nullptr;
};

constexpr char GeneratedCodeCacheTest::kInitialUrl[];
constexpr char GeneratedCodeCacheTest::kInitialOrigin[];
constexpr char GeneratedCodeCacheTest::kInitialData[];
const size_t GeneratedCodeCacheTest::kMaxSizeInBytes;

TEST_P(GeneratedCodeCacheTest, GetResourceURLFromKey) {
  // These must be kept in sync with the values in generated_code_cache.cc.
  constexpr char kPrefix[] = "_key";
  constexpr char kSeparator[] = " \n";

  // Test that we correctly extract the resource URL from a key.
  std::string key(kPrefix);
  key.append(kInitialUrl);
  key.append(kSeparator);
  key.append(kInitialOrigin);

  EXPECT_EQ(GeneratedCodeCache::GetResourceURLFromKey(key), kInitialUrl);

  // Invalid key formats should return the empty string.
  ASSERT_TRUE(GeneratedCodeCache::GetResourceURLFromKey("").empty());
  ASSERT_TRUE(GeneratedCodeCache::GetResourceURLFromKey("foobar").empty());
  ASSERT_TRUE(
      GeneratedCodeCache::GetResourceURLFromKey(
          "43343250B630900F20597168708E14F17A6263F5251FCA10746EA7BDEA881085")
          .empty());
}

TEST_P(GeneratedCodeCacheTest, CheckResponseTime) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data = "SerializedCodeForScript";
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, FetchEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(kInitialData, received_data_);
}

TEST_P(GeneratedCodeCacheTest, WriteEntry) {
  GURL new_url("http://example1.com/script.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data = "SerializedCodeForScript";
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteLargeEntry) {
  GURL new_url("http://example1.com/script.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, large_data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(large_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteVeryLargeEntry) {
  GURL new_url("http://example1.com/script.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kVeryLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, large_data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(large_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, DeleteEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  DeleteFromCache(url, origin_lock);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
}

TEST_P(GeneratedCodeCacheTest, WriteEntryWithEmptyData) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, std::string(), response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteEntryFailure) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  base::Time response_time = base::Time::Now();
  std::string too_big_data(kMaxSizeInBytes * 8, 0);
  WriteToCache(url, origin_lock, too_big_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  // Fetch should return empty data, with invalid response time.
  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
  EXPECT_EQ(base::Time(), received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteEntryFailureOutOfOrder) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  // Dooming adds pending activity for all entries. This makes the following
  // write block for stream 0, while the stream 1 write fails synchronously. The
  // two callbacks are received in reverse order.
  DoomAll();
  base::Time response_time = base::Time::Now();
  std::string too_big_data(kMaxSizeInBytes * 8, 0);
  WriteToCache(url, origin_lock, too_big_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  // Fetch should return empty data, with invalid response time.
  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
  EXPECT_EQ(base::Time(), received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, FetchEntryPendingOp) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(kInitialData, received_data_);
}

TEST_P(GeneratedCodeCacheTest, WriteEntryPendingOp) {
  GURL new_url("http://example1.com/script1.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data = "SerializedCodeForScript";
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteLargeEntryPendingOp) {
  GURL new_url("http://example1.com/script1.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, large_data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(large_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, WriteVeryLargeEntryPendingOp) {
  GURL new_url("http://example1.com/script1.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kVeryLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(new_url, origin_lock, large_data, response_time);
  FetchFromCache(new_url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(large_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, DeleteEntryPendingOp) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript);
  DeleteFromCache(url, origin_lock);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
}

TEST_P(GeneratedCodeCacheTest, UpdateDataOfExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string new_data = "SerializedCodeForScriptOverwrite";
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, UpdateDataOfSmallExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string new_data(kLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, UpdateDataOfLargeExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, large_data, response_time);
  std::string new_data = large_data + "Overwrite";
  response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, UpdateDataOfVeryLargeExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kVeryLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, large_data, response_time);
  std::string new_data = large_data + "Overwrite";
  response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, TruncateDataOfLargeExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, large_data, response_time);
  std::string new_data = "SerializedCodeForScriptOverwrite";
  response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, TruncateDataOfVeryLargeExistingEntry) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string large_data(kVeryLargeSizeInBytes, 'x');
  base::Time response_time = base::Time::Now();
  WriteToCache(url, origin_lock, large_data, response_time);
  std::string new_data = "SerializedCodeForScriptOverwrite";
  response_time = base::Time::Now();
  WriteToCache(url, origin_lock, new_data, response_time);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(new_data, received_data_);
  EXPECT_EQ(response_time, received_response_time_);
}

TEST_P(GeneratedCodeCacheTest, FetchFailsForNonexistingOrigin) {
  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  GURL new_origin_lock = GURL("http://not-example.com");
  FetchFromCache(GURL(kInitialUrl), new_origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  ASSERT_TRUE(received_null_);
}

TEST_P(GeneratedCodeCacheTest, FetchEntriesFromSameOrigin) {
  GURL url("http://example.com/script.js");
  GURL second_url("http://script.com/one.js");
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data_first_resource = "SerializedCodeForFirstResource";
  WriteToCache(url, origin_lock, data_first_resource, base::Time());

  std::string data_second_resource = "SerializedCodeForSecondResource";
  WriteToCache(second_url, origin_lock, data_second_resource, base::Time());

  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(data_first_resource, received_data_);

  FetchFromCache(second_url, origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(data_second_resource, received_data_);
}

TEST_P(GeneratedCodeCacheTest, FetchSucceedsFromDifferentOrigins) {
  GURL url("http://example.com/script.js");
  GURL origin_lock = GURL("http://example.com");
  GURL origin_lock1 = GURL("http://example1.com");

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data_origin = "SerializedCodeForFirstOrigin";
  WriteToCache(url, origin_lock, data_origin, base::Time());

  std::string data_origin1 = "SerializedCodeForSecondOrigin";
  WriteToCache(url, origin_lock1, data_origin1, base::Time());

  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(data_origin, received_data_);

  FetchFromCache(url, origin_lock1);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(data_origin1, received_data_);
}

TEST_P(GeneratedCodeCacheTest, VeryLargeEntriesAreMerged) {
  GURL url("http://example.com/script.js");
  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);

  // Write more copies of the same resource than the cache can hold unless they
  // are merged by content.
  for (size_t i = 0; i < 2 * kMaxSizeInBytes / kVeryLargeSizeInBytes; ++i) {
    GURL origin_lock = GURL(std::string("http://example") +
                            base::NumberToString(i) + std::string(".com"));
    std::string large_data(kVeryLargeSizeInBytes, 'x');
    WriteToCache(url, origin_lock, large_data, base::Time());
  }

  for (size_t i = 0; i < 2 * kMaxSizeInBytes / kVeryLargeSizeInBytes; ++i) {
    GURL origin_lock = GURL(std::string("http://example") +
                            base::NumberToString(i) + std::string(".com"));
    std::string large_data(kVeryLargeSizeInBytes, 'x');
    FetchFromCache(url, origin_lock);
    task_environment_.RunUntilIdle();
    ASSERT_TRUE(received_);
    EXPECT_EQ(large_data, received_data_);
    received_ = false;
    received_data_ = std::string();
  }
}

TEST_P(GeneratedCodeCacheTest, StressVeryLargeEntries) {
  GURL url("http://example.com/script.js");
  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  // Fill the cache with very large data keyed by the SHA-256 checksum.
  char data1 = 0;
  for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1;
       ++i, ++data1) {
    GURL origin_lock = GURL(std::string("http://example") +
                            base::NumberToString(i) + std::string(".com"));
    std::string large_data(kVeryLargeSizeInBytes, data1);
    WriteToCache(url, origin_lock, large_data, base::Time());
  }

  // Fill the cache with new data. The old entries should be purged to make
  // room for the new ones.
  char data2 = -128;
  for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1;
       ++i, ++data2) {
    GURL origin_lock = GURL(std::string("http://example") +
                            base::NumberToString(i) + std::string(".com"));
    std::string large_data(kVeryLargeSizeInBytes, data2);
    WriteToCache(url, origin_lock, large_data, base::Time());
  }

  data2 = -128;
  for (size_t i = 0; i < kMaxSizeInBytes / kVeryLargeSizeInBytes - 1;
       ++i, ++data2) {
    GURL origin_lock = GURL(std::string("http://example") +
                            base::NumberToString(i) + std::string(".com"));
    FetchFromCache(url, origin_lock);
    task_environment_.RunUntilIdle();
    // We can't depend too strongly on the disk cache storage heuristic. Verify
    // that if we received data, it's what we wrote.
    if (!received_null_) {
      std::string large_data(kVeryLargeSizeInBytes, data2);
      EXPECT_EQ(large_data, received_data_);
      received_ = false;
      received_data_ = std::string();
    }
  }
}

TEST_P(GeneratedCodeCacheTest, FetchSucceedsEmptyOriginLock) {
  GURL url("http://example.com/script.js");
  GURL origin_lock = GURL("");

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string data = "SerializedCodeForEmptyOrigin";
  WriteToCache(url, origin_lock, data, base::Time());

  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(data, received_data_);
}

TEST_P(GeneratedCodeCacheTest, FetchEmptyOriginVsValidOriginLocks) {
  GURL url("http://example.com/script.js");
  GURL empty_origin_lock = GURL("");
  GURL origin_lock = GURL("http://example.com");

  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript);
  std::string empty_origin_data = "SerializedCodeForEmptyOrigin";
  WriteToCache(url, empty_origin_lock, empty_origin_data, base::Time());

  std::string valid_origin_data = "SerializedCodeForValidOrigin";
  WriteToCache(url, origin_lock, valid_origin_data, base::Time());

  FetchFromCache(url, empty_origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(empty_origin_data, received_data_);

  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(received_);
  EXPECT_EQ(valid_origin_data, received_data_);
}

TEST_P(GeneratedCodeCacheTest, WasmCache) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  InitializeCache(GeneratedCodeCache::CodeCacheType::kWebAssembly);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  ASSERT_TRUE(received_);
  EXPECT_EQ(kInitialData, received_data_);
}

TEST_P(GeneratedCodeCacheTest, TestFailedBackendOpening) {
  GURL url(kInitialUrl);
  GURL origin_lock = GURL(kInitialOrigin);

  // Clear cache_path_ so the backend initialization fails.
  cache_path_.clear();
  InitializeCacheAndReOpen(GeneratedCodeCache::CodeCacheType::kJavaScript);
  FetchFromCache(url, origin_lock);
  task_environment_.RunUntilIdle();

  // We should still receive a callback.
  ASSERT_TRUE(received_);
  // We shouldn't receive any data.
  ASSERT_TRUE(received_null_);
}

// Tests the embedder specifying custom WebUI hostnames for metrics reporting.
class TestBrowserClient : public ContentBrowserClient {
 public:
  TestBrowserClient() = default;
  TestBrowserClient(const TestBrowserClient&) = delete;
  TestBrowserClient& operator=(const TestBrowserClient&) = delete;
  ~TestBrowserClient() override = default;

  // ContentBrowserClient:
  std::string GetWebUIHostnameForCodeCacheMetrics(
      const GURL& webui_url) const override {
    if (webui_url.GetHost() == "foo") {
      return "Foo";
    }
    if (webui_url.GetHost() == "bar") {
      return "Bar";
    }
    return std::string();
  }
};

class GeneratedCodeCacheMetricsTest : public GeneratedCodeCacheTest {
 public:
  GeneratedCodeCacheMetricsTest() {
    content::SetBrowserClientForTesting(&browser_client_);
  }
  ~GeneratedCodeCacheMetricsTest() override {
    content::SetBrowserClientForTesting(nullptr);
  }

 private:
  TestBrowserClient browser_client_;
};

TEST_P(GeneratedCodeCacheMetricsTest, JSWebUIMetricsWithEmbedderHostnames) {
  constexpr char kResource1Url[] = "chrome://foo/resource.js";
  constexpr char kResource2Url[] = "chrome://bar/resource.js";
  constexpr char kOriginUrl[] = "chrome://foo/";
  InitializeCache(GeneratedCodeCache::CodeCacheType::kWebUIJavaScript,
                  kResource1Url, kOriginUrl);
  base::HistogramTester histogram_tester;

  // Simulate a request for two resources against different data sources for a
  // single origin.
  generated_code_cache_->CollectStatisticsForTest(
      GURL(kResource1Url), GURL(kOriginUrl),
      content::GeneratedCodeCache::CacheEntryStatus::kUpdate);
  generated_code_cache_->CollectStatisticsForTest(
      GURL(kResource2Url), GURL(kOriginUrl),
      content::GeneratedCodeCache::CacheEntryStatus::kHit);

  // WebUI scripts report both the generic and WebUI specific metrics.
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1);
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.WebUI.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1);
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.WebUI.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);

  // There should be two source specific resource hits.
  histogram_tester.ExpectUniqueSample(
      "SiteIsolatedCodeCache.JS.WebUI.Foo.Resource.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1);
  histogram_tester.ExpectUniqueSample(
      "SiteIsolatedCodeCache.JS.WebUI.Bar.Resource.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);

  // Both resource requests should be reported against the same origin.
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.WebUI.Foo.Origin.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kUpdate, 1);
  histogram_tester.ExpectBucketCount(
      "SiteIsolatedCodeCache.JS.WebUI.Foo.Origin.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);
}

TEST_P(GeneratedCodeCacheMetricsTest, JavaScriptMetrics) {
  constexpr char kResourceUrl[] = "https://foo/resource.js";
  constexpr char kOriginUrl[] = "https://foo/";
  InitializeCache(GeneratedCodeCache::CodeCacheType::kJavaScript, kResourceUrl,
                  kOriginUrl);
  base::HistogramTester histogram_tester;

  generated_code_cache_->CollectStatisticsForTest(
      GURL(kResourceUrl), GURL(kOriginUrl),
      content::GeneratedCodeCache::CacheEntryStatus::kHit);
  histogram_tester.ExpectUniqueSample(
      "SiteIsolatedCodeCache.JS.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);
}

TEST_P(GeneratedCodeCacheMetricsTest, WebAssemblyMetrics) {
  constexpr char kResourceUrl[] = "https://foo/resource.wasm";
  constexpr char kOriginUrl[] = "https://foo/";
  InitializeCache(GeneratedCodeCache::CodeCacheType::kWebAssembly, kResourceUrl,
                  kOriginUrl);
  base::HistogramTester histogram_tester;

  generated_code_cache_->CollectStatisticsForTest(
      GURL(kResourceUrl), GURL(kOriginUrl),
      content::GeneratedCodeCache::CacheEntryStatus::kHit);
  histogram_tester.ExpectUniqueSample(
      "SiteIsolatedCodeCache.WASM.Behaviour",
      content::GeneratedCodeCache::CacheEntryStatus::kHit, 1);
}

INSTANTIATE_TEST_SUITE_P(GeneratedCodeCacheTest,
                         GeneratedCodeCacheTest,
                         testing::Bool());
INSTANTIATE_TEST_SUITE_P(GeneratedCodeCacheMetricsTest,
                         GeneratedCodeCacheMetricsTest,
                         testing::Bool());

}  // namespace content