blob: 99cdce2c3062e9d51a1d6daa5bdc65027229c053 [file] [log] [blame]
// Copyright 2020 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 "content/browser/appcache/appcache_cache_test_helper.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/post_task.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_host.h"
#include "content/browser/appcache/appcache_response_info.h"
#include "content/browser/appcache/appcache_update_job.h"
#include "content/browser/appcache/mock_appcache_service.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
#include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
namespace content {
namespace appcache_cache_test_helper_unittest {
class AppCacheCacheTestHelperTest;
// Values should match values used in appcache_update_job.cc.
const base::TimeDelta kOneHour = base::TimeDelta::FromHours(1);
const char kManifest1Contents[] =
"CACHE MANIFEST\n"
"explicit1\n"
"FALLBACK:\n"
"fallback1 fallback1a\n"
"NETWORK:\n"
"*\n";
// There are a handful of http accessible resources that we need to conduct
// these tests. Instead of running a separate server to host these resources,
// we mock them up.
class MockHttpServer {
public:
static GURL GetMockUrl(const std::string& path) {
return GURL("http://mockhost/" + path);
}
static void GetMockResponse(const std::string& path,
std::string* headers,
std::string* body) {
const char not_found_headers[] =
"HTTP/1.1 404 NOT FOUND\n"
"\n";
(*headers) = std::string(not_found_headers, base::size(not_found_headers));
(*body) = "";
}
};
inline bool operator==(const AppCacheNamespace& lhs,
const AppCacheNamespace& rhs) {
return lhs.type == rhs.type && lhs.namespace_url == rhs.namespace_url &&
lhs.target_url == rhs.target_url;
}
class MockFrontend : public blink::mojom::AppCacheFrontend {
public:
MockFrontend()
: ignore_progress_events_(false),
verify_progress_events_(false),
last_progress_total_(-1),
last_progress_complete_(-1),
start_update_trigger_(
blink::mojom::AppCacheEventID::APPCACHE_CHECKING_EVENT),
update_(nullptr) {}
void CacheSelected(blink::mojom::AppCacheInfoPtr info) override {}
void EventRaised(blink::mojom::AppCacheEventID event_id) override {
raised_events_.push_back(event_id);
// Trigger additional updates if requested.
if (event_id == start_update_trigger_ && update_) {
for (AppCacheHost* host : update_hosts_) {
update_->StartUpdate(
host, (host ? host->pending_master_entry_url() : GURL()));
}
update_hosts_.clear(); // only trigger once
}
}
void ErrorEventRaised(
blink::mojom::AppCacheErrorDetailsPtr details) override {
error_message_ = details->message;
EventRaised(blink::mojom::AppCacheEventID::APPCACHE_ERROR_EVENT);
}
void ProgressEventRaised(const GURL& url,
int32_t num_total,
int32_t num_complete) override {
if (!ignore_progress_events_)
EventRaised(blink::mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT);
if (verify_progress_events_) {
EXPECT_GE(num_total, num_complete);
EXPECT_GE(num_complete, 0);
if (last_progress_total_ == -1) {
// Should start at zero.
EXPECT_EQ(0, num_complete);
} else {
// Total should be stable and complete should bump up by one at a time.
EXPECT_EQ(last_progress_total_, num_total);
EXPECT_EQ(last_progress_complete_ + 1, num_complete);
}
// Url should be valid for all except the 'final' event.
if (num_total == num_complete)
EXPECT_TRUE(url.is_empty());
else
EXPECT_TRUE(url.is_valid());
last_progress_total_ = num_total;
last_progress_complete_ = num_complete;
}
}
void LogMessage(blink::mojom::ConsoleMessageLevel log_level,
const std::string& message) override {}
void SetSubresourceFactory(
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory)
override {}
void AddExpectedEvent(blink::mojom::AppCacheEventID event_id) {
DCHECK(!ignore_progress_events_ ||
event_id != blink::mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT);
expected_events_.push_back(event_id);
}
void SetIgnoreProgressEvents(bool ignore) {
// Some tests involve joining new hosts to an already running update job
// or intentionally failing. The timing and sequencing of the progress
// events generated by an update job are dependent on the behavior of
// an external HTTP server. For jobs that do not run fully till completion,
// due to either joining late or early exit, we skip monitoring the
// progress events to avoid flakiness.
ignore_progress_events_ = ignore;
}
void SetVerifyProgressEvents(bool verify) {
verify_progress_events_ = verify;
}
void TriggerAdditionalUpdates(blink::mojom::AppCacheEventID trigger_event,
AppCacheUpdateJob* update) {
start_update_trigger_ = trigger_event;
update_ = update;
}
void AdditionalUpdateHost(AppCacheHost* host) {
update_hosts_.push_back(host);
}
using RaisedEvents = std::vector<blink::mojom::AppCacheEventID>;
RaisedEvents raised_events_;
std::string error_message_;
// Set the expected events if verification needs to happen asynchronously.
RaisedEvents expected_events_;
std::string expected_error_message_;
bool ignore_progress_events_;
bool verify_progress_events_;
int last_progress_total_;
int last_progress_complete_;
// Add ability for frontend to add master entries to an inprogress update.
blink::mojom::AppCacheEventID start_update_trigger_;
AppCacheUpdateJob* update_;
std::vector<AppCacheHost*> update_hosts_;
};
class AppCacheCacheTestHelperTest : public testing::Test {
public:
AppCacheCacheTestHelperTest()
: process_id_(123),
weak_partition_factory_(static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(&browser_context_))) {}
void SetUp() override {
ChildProcessSecurityPolicyImpl::GetInstance()->Add(process_id_,
&browser_context_);
}
void TearDown() override {
ChildProcessSecurityPolicyImpl::GetInstance()->Remove(process_id_);
}
// Use a separate IO thread to run a test. Thread will be destroyed
// when it goes out of scope.
template <class Method>
void RunTestOnUIThread(Method method) {
base::RunLoop run_loop;
test_completed_cb_ = run_loop.QuitClosure();
base::PostTask(FROM_HERE, {BrowserThread::UI},
base::BindOnce(method, base::Unretained(this)));
run_loop.Run();
}
void BasicStart() {
MakeService();
group_ = base::MakeRefCounted<AppCacheGroup>(
service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
111);
// Create a cache without a manifest entry. The manifest entry will be
// added later.
AppCache* cache = MakeCacheForGroup(service_->storage()->NewCacheId(), -1);
MockFrontend* frontend = MakeMockFrontend();
AppCacheHost* host = MakeHost(frontend);
host->AssociateCompleteCache(cache);
AppCacheCacheTestHelper::CacheEntries cache_entries;
// Add cache entry for manifest.
const char data[] =
"HTTP/1.1 200 OK\0"
"Last-Modified: Sat, 29 Oct 1994 19:43:31 GMT\0";
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>(
std::string(data, base::size(data)));
std::unique_ptr<net::HttpResponseInfo> response_info =
std::make_unique<net::HttpResponseInfo>();
response_info->headers = std::move(headers);
AppCacheCacheTestHelper::AddCacheEntry(
&cache_entries, group_->manifest_url(), AppCacheEntry::EXPLICIT,
/*expect_if_modified_since=*/"Sat, 29 Oct 1994 19:43:31 GMT",
/*expect_if_none_match=*/std::string(), /*headers_allowed=*/true,
std::move(response_info), kManifest1Contents);
cache_helper_ = std::make_unique<AppCacheCacheTestHelper>(
service_.get(), group_->manifest_url(), cache, std::move(cache_entries),
base::BindOnce(&AppCacheCacheTestHelperTest::BasicWriteFinished,
base::Unretained(this)));
cache_helper_->Write();
// Continues async in |BasicWriteFinished|.
}
void BasicWriteFinished(int result) {
cache_helper_->PrepareForRead(
group_->newest_complete_cache(),
base::BindOnce(&AppCacheCacheTestHelperTest::BasicReadFinished,
base::Unretained(this)));
cache_helper_->Read();
// Continues async in |BasicReadFinished|.
}
void BasicReadFinished() {
EXPECT_EQ(cache_helper_->cache_entries().size(),
cache_helper_->read_cache_entries().size());
for (auto it = cache_helper_->cache_entries().begin();
it != cache_helper_->cache_entries().end(); ++it) {
auto read_it = cache_helper_->read_cache_entries().find(it->first);
EXPECT_EQ(it->second->response_info->headers->raw_headers(),
read_it->second->response_info->headers->raw_headers());
EXPECT_EQ(it->second->body, read_it->second->body);
}
Finished();
}
void Finished() {
// We unwind the stack prior to finishing up to let stack-based objects
// get deleted.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppCacheCacheTestHelperTest::FinishedUnwound,
base::Unretained(this)));
}
void FinishedUnwound() {
// Clean up everything that was created on the IO thread.
cache_helper_.reset();
protect_newest_cache_ = nullptr;
group_ = nullptr;
hosts_.clear();
frontends_.clear();
response_infos_.clear();
service_.reset(nullptr);
std::move(test_completed_cb_).Run();
}
void MakeService() {
service_ = std::make_unique<MockAppCacheService>(
weak_partition_factory_.GetWeakPtr());
}
AppCache* MakeCacheForGroup(int64_t cache_id, int64_t manifest_response_id) {
return MakeCacheForGroup(cache_id, group_->manifest_url(),
manifest_response_id);
}
AppCache* MakeCacheForGroup(int64_t cache_id,
const GURL& manifest_entry_url,
int64_t manifest_response_id) {
AppCache* cache = new AppCache(service_->storage(), cache_id);
cache->set_complete(true);
cache->set_manifest_parser_version(1);
cache->set_manifest_scope("/");
cache->set_update_time(base::Time::Now() - kOneHour);
group_->AddCache(cache);
group_->set_last_full_update_check_time(cache->update_time());
// Add manifest entry to cache.
if (manifest_response_id >= 0) {
cache->AddEntry(manifest_entry_url, AppCacheEntry(AppCacheEntry::MANIFEST,
manifest_response_id));
}
// Specific tests that expect a newer time should set
// expect_full_update_time_newer_than_ which causes this
// equality expectation to be ignored.
expect_full_update_time_equal_to_ = cache->update_time();
return cache;
}
AppCacheHost* MakeHost(blink::mojom::AppCacheFrontend* frontend) {
constexpr int kRenderFrameIdForTests = 456;
hosts_.push_back(std::make_unique<AppCacheHost>(
base::UnguessableToken::Create(), process_id_, kRenderFrameIdForTests,
mojo::NullRemote(), service_.get()));
hosts_.back()->set_frontend_for_testing(frontend);
return hosts_.back().get();
}
AppCacheResponseInfo* MakeAppCacheResponseInfo(
const GURL& manifest_url,
int64_t response_id,
const std::string& raw_headers) {
std::unique_ptr<net::HttpResponseInfo> http_info =
std::make_unique<net::HttpResponseInfo>();
http_info->headers =
base::MakeRefCounted<net::HttpResponseHeaders>(raw_headers);
auto info = base::MakeRefCounted<AppCacheResponseInfo>(
service_->storage()->GetWeakPtr(), manifest_url, response_id,
std::move(http_info), 0);
response_infos_.emplace_back(info);
return info.get();
}
MockFrontend* MakeMockFrontend() {
frontends_.push_back(std::make_unique<MockFrontend>());
return frontends_.back().get();
}
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<MockAppCacheService> service_;
scoped_refptr<AppCacheGroup> group_;
scoped_refptr<AppCache> protect_newest_cache_;
base::OnceClosure test_completed_cb_;
std::unique_ptr<AppCacheResponseWriter> response_writer_;
std::unique_ptr<AppCacheCacheTestHelper> cache_helper_;
// Hosts used by an async test that need to live until update job finishes.
// Otherwise, test can put host on the stack instead of here.
std::vector<std::unique_ptr<AppCacheHost>> hosts_;
// Response infos used by an async test that need to live until update job
// finishes.
std::vector<scoped_refptr<AppCacheResponseInfo>> response_infos_;
// Flag indicating if test cares to verify the update after update finishes.
base::Time expect_full_update_time_equal_to_;
std::vector<std::unique_ptr<MockFrontend>>
frontends_; // to check expected events
AppCache::EntryMap expect_extra_entries_;
std::map<GURL, int64_t> expect_response_ids_;
content::TestBrowserContext browser_context_;
const int process_id_;
base::WeakPtrFactory<StoragePartitionImpl> weak_partition_factory_;
};
TEST_F(AppCacheCacheTestHelperTest, Basic) {
RunTestOnUIThread(&AppCacheCacheTestHelperTest::BasicStart);
}
} // namespace appcache_cache_test_helper_unittest
} // namespace content