blob: 5199c9cac9b60cf5fbabcab8f89a1003925b0f45 [file] [log] [blame]
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/loader/fetcher_cache.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "cobalt/loader/fetcher.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace cobalt {
namespace loader {
namespace {
constexpr int kFetcherCapacity = 1024 * 1024;
class StubFetcher : public Fetcher {
public:
explicit StubFetcher(Handler* handler) : Fetcher(handler) {}
~StubFetcher() override {}
void FireResponseStarted(
const scoped_refptr<net::HttpResponseHeaders>& headers) {
auto response_type = handler()->OnResponseStarted(this, headers);
if (response_type == kLoadResponseAbort) {
return;
}
}
void FireReceived(const char* data, size_t size) {
handler()->OnReceived(this, data, size);
}
void FireDone() { handler()->OnDone(this); }
void FireFetcherProcess(
const scoped_refptr<net::HttpResponseHeaders>& headers, const char* data,
size_t size) {
LoadResponseType response = handler()->OnResponseStarted(this, headers);
if (response == kLoadResponseContinue) {
handler()->OnReceived(this, data, size);
handler()->OnDone(this);
}
}
};
class StubFetcherHandler : public Fetcher::Handler {
public:
// From Fetcher::Handler.
void OnReceived(Fetcher* fetcher, const char* data, size_t size) override {}
void OnDone(Fetcher* fetcher) override {}
void OnError(Fetcher* fetcher, const std::string& error_message) override {}
};
struct StubFetcherFactory {
public:
StubFetcherFactory() {}
// Way to access the last fetcher created by the fetcher factory.
StubFetcher* fetcher;
std::unique_ptr<Fetcher> Create(Fetcher::Handler* handler) {
fetcher = new StubFetcher(handler);
return base::WrapUnique<Fetcher>(fetcher);
}
Loader::FetcherCreator GetFetcherCreator() {
return base::Bind(&StubFetcherFactory::Create, base::Unretained(this));
}
};
std::unique_ptr<Fetcher> CreateFetcher(Fetcher::Handler* handler) {
StubFetcher* fetcher = new StubFetcher(handler);
return base::WrapUnique<Fetcher>(fetcher);
}
} // namespace
class FetcherCacheTest : public ::testing::Test {
protected:
class OngoingFetcherFactory {
public:
OngoingFetcherFactory(const char* name, size_t capacity) {
fetcher_cache_ = base::MakeRefCounted<FetcherCache>(name, capacity);
}
~OngoingFetcherFactory() {
if (fetcher_cache_) {
fetcher_cache_->DestroySoon();
}
}
size_t size() const { return fetcher_cache_->size(); }
size_t capacity() const { return fetcher_cache_->capacity(); }
Loader::FetcherCreator MakeCachedFetcherCreator(
const GURL& url, const Loader::FetcherCreator& real_fetcher_creator) {
return fetcher_cache_->GetFetcherCreator(url, real_fetcher_creator);
}
private:
scoped_refptr<FetcherCache> fetcher_cache_;
};
const std::string data_ = "Test string! Test string! Test!!";
base::test::TaskEnvironment scoped_task_environment_;
OngoingFetcherFactory fetcher_factory_{"cache_name", kFetcherCapacity};
};
TEST_F(FetcherCacheTest, CtorAndDtor) {
EXPECT_EQ(kFetcherCapacity, fetcher_factory_.capacity());
}
TEST_F(FetcherCacheTest, MakeFetcher) {
auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com"), base::Bind(&CreateFetcher));
EXPECT_EQ(0, fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, SunnyDay) {
StubFetcherHandler handler;
StubFetcherFactory stub_fetcher_factory;
auto cb = stub_fetcher_factory.GetFetcherCreator();
auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com"), cb);
auto fetcher = fetcher_creator.Run(&handler);
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory.fetcher),
data_.c_str(), data_.size()));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory.fetcher)));
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, DuplicateFetcherCache) {
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
std::vector<StubFetcherFactory> stub_fetcher_factorys;
std::vector<Loader::FetcherCreator> fetcher_creators;
std::vector<std::unique_ptr<Fetcher>> fetchers;
for (size_t i = 0; i < 2; i++) {
StubFetcherFactory stub_fetcher_factory;
Loader::FetcherCreator fetcher_creator =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com"),
stub_fetcher_factory.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
stub_fetcher_factorys.push_back(stub_fetcher_factory);
fetcher_creators.push_back(fetcher_creator);
fetchers.push_back(std::move(fetcher));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireFetcherProcess,
base::Unretained(stub_fetcher_factorys[i].fetcher),
headers, data_.c_str(), data_.size()));
}
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, MultipleFetcherCache) {
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
const size_t kMaxNumCache = 10;
std::vector<StubFetcherFactory> stub_fetcher_factorys;
std::vector<Loader::FetcherCreator> fetcher_creators;
std::vector<std::unique_ptr<Fetcher>> fetchers;
for (size_t i = 0; i < kMaxNumCache; i++) {
StubFetcherFactory stub_fetcher_factory;
Loader::FetcherCreator fetcher_creator =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com" + base::NumberToString(i)),
stub_fetcher_factory.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
stub_fetcher_factorys.push_back(stub_fetcher_factory);
fetcher_creators.push_back(fetcher_creator);
fetchers.push_back(std::move(fetcher));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireFetcherProcess,
base::Unretained(stub_fetcher_factorys[i].fetcher),
headers, data_.c_str(), data_.size()));
}
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity() * kMaxNumCache, fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, MaxFetcherCache) {
const size_t capacity = kFetcherCapacity;
const size_t data_capacity = data_.capacity();
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
// kMaxNumCache = ceil(capacity / data_.capacity()) + 1, this will force
// to write a new cache and delete an old cache
const size_t kMaxNumCache = 1 + (capacity / data_capacity);
std::vector<StubFetcherFactory> stub_fetcher_factorys;
std::vector<Loader::FetcherCreator> fetcher_creators;
std::vector<std::unique_ptr<Fetcher>> fetchers;
for (size_t i = 0; i < kMaxNumCache; i++) {
StubFetcherFactory stub_fetcher_factory;
Loader::FetcherCreator fetcher_creator =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com" + base::NumberToString(i)),
stub_fetcher_factory.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
stub_fetcher_factorys.push_back(stub_fetcher_factory);
fetcher_creators.push_back(fetcher_creator);
fetchers.push_back(std::move(fetcher));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireFetcherProcess,
base::Unretained(stub_fetcher_factorys[i].fetcher),
headers, data_.c_str(), data_.size()));
}
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_capacity * (kMaxNumCache - 1), fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, DisorderedSameFetcherCache) {
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
Loader::FetcherCreator fetcher_creator1 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com"),
stub_fetcher_factory1.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
Loader::FetcherCreator fetcher_creator2 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example.com"),
stub_fetcher_factory2.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
// Task 1: start and receive
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory1.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory1.fetcher),
data_.c_str(), data_.size()));
// Task 2: start and receive
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory2.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory2.fetcher),
data_.c_str(), data_.size()));
// Task 1: done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory1.fetcher)));
// Task 2: done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory2.fetcher)));
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, DisorderedDiffFetcherCache) {
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
Loader::FetcherCreator fetcher_creator1 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example1.com"),
stub_fetcher_factory1.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
Loader::FetcherCreator fetcher_creator2 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example2.com"),
stub_fetcher_factory2.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
// Task 1: start and receive
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory1.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory1.fetcher),
data_.c_str(), data_.size()));
// Task 2: start and receive
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory2.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory2.fetcher),
data_.c_str(), data_.size()));
// Task 1: done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory1.fetcher)));
// Task 2: done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory2.fetcher)));
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size());
}
TEST_F(FetcherCacheTest, DisorderedSlowFetcherCache) {
StubFetcherHandler handler;
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
Loader::FetcherCreator fetcher_creator1 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example1.com"),
stub_fetcher_factory1.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
Loader::FetcherCreator fetcher_creator2 =
fetcher_factory_.MakeCachedFetcherCreator(
GURL("https://example2.com"),
stub_fetcher_factory2.GetFetcherCreator());
std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
// Task 1: start and receive
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory1.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory1.fetcher),
data_.c_str(), data_.size()));
// Task 2: start, receive, and done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireResponseStarted,
base::Unretained(stub_fetcher_factory2.fetcher), headers));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
base::Unretained(stub_fetcher_factory2.fetcher),
data_.c_str(), data_.size()));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory2.fetcher)));
// Task 1: done
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&StubFetcher::FireDone,
base::Unretained(stub_fetcher_factory1.fetcher)));
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size());
}
} // namespace loader
} // namespace cobalt