blob: 71cc5fb9d18e3dfbcb82463b0bead88645cd30e2 [file] [log] [blame]
// Copyright 2015 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/service_worker/service_worker_read_from_cache_job.h"
#include <stdint.h>
#include <memory>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/io_buffer.h"
#include "net/base/test_completion_callback.h"
#include "net/http/http_response_headers.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
const int64_t kRegistrationId = 1;
const int64_t kVersionId = 2;
const int64_t kMainScriptResourceId = 10;
const int64_t kImportedScriptResourceId = 11;
const int64_t kNonExistentResourceId = 12;
const int64_t kResourceSize = 100;
void DidStoreRegistration(ServiceWorkerStatusCode* status_out,
const base::Closure& quit_closure,
ServiceWorkerStatusCode status) {
*status_out = status;
quit_closure.Run();
}
void DidFindRegistration(
ServiceWorkerStatusCode* status_out,
const base::Closure& quit_closure,
ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
*status_out = status;
quit_closure.Run();
}
} // namespace
class ServiceWorkerReadFromCacheJobTest : public testing::Test {
public:
ServiceWorkerReadFromCacheJobTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
main_script_(kMainScriptResourceId,
GURL("http://example.com/main.js"),
kResourceSize),
imported_script_(kImportedScriptResourceId,
GURL("http://example.com/imported.js"),
kResourceSize),
test_job_interceptor_(nullptr) {}
~ServiceWorkerReadFromCacheJobTest() override {}
void SetUp() override {
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
InitializeStorage();
url_request_context_.reset(new net::TestURLRequestContext(true));
// The |test_job_factory_| takes ownership of the interceptor.
test_job_interceptor_ = new net::TestJobInterceptor();
EXPECT_TRUE(test_job_factory_.SetProtocolHandler(
url::kHttpScheme, base::WrapUnique(test_job_interceptor_)));
url_request_context_->set_job_factory(&test_job_factory_);
url_request_context_->Init();
}
void InitializeStorage() {
base::RunLoop run_loop;
context()->storage()->LazyInitialize(run_loop.QuitClosure());
run_loop.Run();
// Populate a registration in the storage.
registration_ = new ServiceWorkerRegistration(
ServiceWorkerRegistrationOptions(GURL("http://example.com/scope")),
kRegistrationId, context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(registration_.get(), main_script_.url,
kVersionId, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
resources.push_back(main_script_);
resources.push_back(imported_script_);
version_->script_cache_map()->SetResources(resources);
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
ASSERT_EQ(SERVICE_WORKER_OK, StoreRegistration());
ASSERT_TRUE(WriteResource(main_script_.resource_id));
ASSERT_TRUE(WriteResource(imported_script_.resource_id));
}
bool WriteResource(int64_t resource_id) {
const char kHttpHeaders[] = "HTTP/1.0 200 OK\0Content-Length: 5\0\0";
const char kHttpBody[] = "Hello";
const int length = arraysize(kHttpBody);
std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
scoped_refptr<net::IOBuffer> body(new net::WrappedIOBuffer(kHttpBody));
std::unique_ptr<ServiceWorkerResponseWriter> writer =
context()->storage()->CreateResponseWriter(resource_id);
std::unique_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo);
info->request_time = base::Time::Now();
info->response_time = base::Time::Now();
info->was_cached = false;
info->headers = new net::HttpResponseHeaders(headers);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer(info.release());
{
net::TestCompletionCallback cb;
writer->WriteInfo(info_buffer.get(), cb.callback());
int rv = cb.WaitForResult();
if (rv < 0)
return false;
}
{
net::TestCompletionCallback cb;
writer->WriteData(body.get(), length, cb.callback());
int rv = cb.WaitForResult();
if (rv < 0)
return false;
}
return true;
}
ServiceWorkerStatusCode StoreRegistration() {
base::RunLoop run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
context()->storage()->StoreRegistration(
registration_.get(), version_.get(),
base::Bind(&DidStoreRegistration, &status, run_loop.QuitClosure()));
run_loop.Run();
return status;
}
ServiceWorkerStatusCode FindRegistration() {
base::RunLoop run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
context()->storage()->FindRegistrationForId(
registration_->id(), registration_->pattern().GetOrigin(),
base::Bind(&DidFindRegistration, &status, run_loop.QuitClosure()));
run_loop.Run();
return status;
}
void StartAndWaitForRequest(net::URLRequest* request) {
request->Start();
// net::TestDelegate quits the loop when the request is completed.
base::RunLoop().RunUntilIdle();
}
ServiceWorkerStatusCode DeduceStartWorkerFailureReason(
ServiceWorkerStatusCode default_code) {
return version_->DeduceStartWorkerFailureReason(default_code);
}
ServiceWorkerContextCore* context() const { return helper_->context(); }
protected:
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
ServiceWorkerDatabase::ResourceRecord main_script_;
ServiceWorkerDatabase::ResourceRecord imported_script_;
// |test_job_interceptor_| is owned by |test_job_factory_|.
net::TestJobInterceptor* test_job_interceptor_;
net::URLRequestJobFactoryImpl test_job_factory_;
std::unique_ptr<net::TestURLRequestContext> url_request_context_;
net::TestDelegate delegate_;
};
TEST_F(ServiceWorkerReadFromCacheJobTest, ReadMainScript) {
// Read the main script from the diskcache.
std::unique_ptr<net::URLRequest> request =
url_request_context_->CreateRequest(main_script_.url,
net::DEFAULT_PRIORITY, &delegate_,
TRAFFIC_ANNOTATION_FOR_TESTS);
test_job_interceptor_->set_main_intercept_job(
base::MakeUnique<ServiceWorkerReadFromCacheJob>(
request.get(), nullptr /* NetworkDelegate */,
RESOURCE_TYPE_SERVICE_WORKER, context()->AsWeakPtr(), version_,
main_script_.resource_id));
StartAndWaitForRequest(request.get());
EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
EXPECT_EQ(0, request->status().error());
EXPECT_EQ(SERVICE_WORKER_OK,
DeduceStartWorkerFailureReason(SERVICE_WORKER_OK));
}
TEST_F(ServiceWorkerReadFromCacheJobTest, ReadImportedScript) {
// Read the imported script from the diskcache.
std::unique_ptr<net::URLRequest> request =
url_request_context_->CreateRequest(imported_script_.url,
net::DEFAULT_PRIORITY, &delegate_,
TRAFFIC_ANNOTATION_FOR_TESTS);
test_job_interceptor_->set_main_intercept_job(
base::MakeUnique<ServiceWorkerReadFromCacheJob>(
request.get(), nullptr /* NetworkDelegate */, RESOURCE_TYPE_SCRIPT,
context()->AsWeakPtr(), version_, imported_script_.resource_id));
StartAndWaitForRequest(request.get());
EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
EXPECT_EQ(0, request->status().error());
EXPECT_EQ(SERVICE_WORKER_OK,
DeduceStartWorkerFailureReason(SERVICE_WORKER_OK));
}
TEST_F(ServiceWorkerReadFromCacheJobTest, ResourceNotFound) {
ASSERT_EQ(SERVICE_WORKER_OK, FindRegistration());
// Populate the script cache map with a nonexistent resource.
ServiceWorkerScriptCacheMap* script_cache_map = version_->script_cache_map();
script_cache_map->resource_map_.clear();
using Record = ServiceWorkerDatabase::ResourceRecord;
std::vector<Record> resources = {
Record(kNonExistentResourceId, main_script_.url, main_script_.size_bytes),
Record(imported_script_.resource_id, imported_script_.url,
imported_script_.size_bytes)};
script_cache_map->SetResources(resources);
// Attempt to read it from the disk cache.
std::unique_ptr<net::URLRequest> request =
url_request_context_->CreateRequest(main_script_.url,
net::DEFAULT_PRIORITY, &delegate_,
TRAFFIC_ANNOTATION_FOR_TESTS);
const int64_t kNonexistentResourceId = 100;
test_job_interceptor_->set_main_intercept_job(
base::MakeUnique<ServiceWorkerReadFromCacheJob>(
request.get(), nullptr /* NetworkDelegate */,
RESOURCE_TYPE_SERVICE_WORKER, context()->AsWeakPtr(), version_,
kNonexistentResourceId));
StartAndWaitForRequest(request.get());
EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
EXPECT_EQ(net::ERR_CACHE_MISS, request->status().error());
EXPECT_EQ(SERVICE_WORKER_ERROR_DISK_CACHE,
DeduceStartWorkerFailureReason(SERVICE_WORKER_OK));
// The version should be doomed by the job.
EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version_->status());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, FindRegistration());
}
} // namespace content