Index: content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc |
diff --git a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc |
index cd90d45f731150edf3217673b3d97d4d506b0dac..5fc56c4c48fbfd60703fa65fbc635784ab93fea8 100644 |
--- a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc |
+++ b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc |
@@ -7,12 +7,15 @@ |
#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_context_request_handler.h" |
+#include "content/browser/service_worker/service_worker_disk_cache.h" |
#include "content/browser/service_worker/service_worker_provider_host.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_utils.h" |
#include "content/common/resource_request_body.h" |
#include "content/public/test/mock_resource_context.h" |
#include "content/public/test/test_browser_thread_bundle.h" |
+#include "net/base/io_buffer.h" |
#include "net/base/load_flags.h" |
#include "net/http/http_response_headers.h" |
#include "net/url_request/url_request_context.h" |
@@ -26,8 +29,6 @@ namespace content { |
namespace { |
-int kMockRenderProcessId = 1224; |
-int kMockProviderId = 1; |
const char kHeaders[] = |
"HTTP/1.1 200 OK\0" |
"Content-Type: text/javascript\0" |
@@ -38,6 +39,14 @@ const char kScriptCode[] = "// no script code\n"; |
void EmptyCallback() { |
} |
+// The blocksize that ServiceWorkerWriteToCacheJob reads/writes at a time. |
+const int kBlockSize = 16 * 1024; |
+const int kNumBlocks = 8; |
+const int kMiddleBlock = 5; |
+ |
+std::string GenerateLongResponse() { |
+ return std::string(kNumBlocks * kBlockSize, 'a'); |
+} |
net::URLRequestJob* CreateNormalURLRequestJob( |
net::URLRequest* request, |
net::NetworkDelegate* network_delegate) { |
@@ -48,6 +57,14 @@ net::URLRequestJob* CreateNormalURLRequestJob( |
true); |
} |
+net::URLRequestJob* CreateResponseJob(const std::string& response_data, |
+ net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) { |
+ return new net::URLRequestTestJob(request, network_delegate, |
+ std::string(kHeaders, arraysize(kHeaders)), |
+ response_data, true); |
+} |
+ |
net::URLRequestJob* CreateInvalidMimeTypeJob( |
net::URLRequest* request, |
net::NetworkDelegate* network_delegate) { |
@@ -141,7 +158,7 @@ class MockHttpProtocolHandler |
typedef base::Callback< |
net::URLRequestJob*(net::URLRequest*, net::NetworkDelegate*)> JobCallback; |
- MockHttpProtocolHandler(ResourceContext* resource_context) |
+ explicit MockHttpProtocolHandler(ResourceContext* resource_context) |
: resource_context_(resource_context) {} |
~MockHttpProtocolHandler() override {} |
@@ -164,7 +181,72 @@ class MockHttpProtocolHandler |
ResourceContext* resource_context_; |
JobCallback create_job_callback_; |
}; |
-} |
+ |
+class ResponseVerifier : public base::RefCounted<ResponseVerifier> { |
+ public: |
+ ResponseVerifier(scoped_ptr<ServiceWorkerResponseReader> reader, |
+ const std::string& expected, |
+ const base::Callback<void(bool)> callback) |
+ : reader_(reader.release()), expected_(expected), callback_(callback) {} |
+ |
+ void Start() { |
+ info_buffer_ = new HttpResponseInfoIOBuffer(); |
+ io_buffer_ = new net::IOBuffer(kBlockSize); |
+ reader_->ReadInfo(info_buffer_.get(), |
+ base::Bind(&ResponseVerifier::OnReadInfoComplete, this)); |
+ bytes_read_ = 0; |
+ } |
+ |
+ void OnReadInfoComplete(int result) { |
+ if (result < 0) { |
+ callback_.Run(false); |
+ return; |
+ } |
+ if (info_buffer_->response_data_size != |
+ static_cast<int>(expected_.size())) { |
+ callback_.Run(false); |
+ return; |
+ } |
+ ReadSomeData(); |
+ } |
+ |
+ void ReadSomeData() { |
+ reader_->ReadData(io_buffer_.get(), kBlockSize, |
+ base::Bind(&ResponseVerifier::OnReadDataComplete, this)); |
+ } |
+ |
+ void OnReadDataComplete(int result) { |
+ if (result < 0) { |
+ callback_.Run(false); |
+ return; |
+ } |
+ if (result == 0) { |
+ callback_.Run(true); |
+ return; |
+ } |
+ std::string str(io_buffer_->data(), result); |
+ std::string expect = expected_.substr(bytes_read_, result); |
+ if (str != expect) { |
+ callback_.Run(false); |
+ return; |
+ } |
+ bytes_read_ += result; |
+ ReadSomeData(); |
+ } |
+ |
+ private: |
+ friend class base::RefCounted<ResponseVerifier>; |
+ ~ResponseVerifier() {} |
+ |
+ scoped_ptr<ServiceWorkerResponseReader> reader_; |
+ const std::string expected_; |
+ base::Callback<void(bool)> callback_; |
+ scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; |
+ scoped_refptr<net::IOBuffer> io_buffer_; |
+ size_t bytes_read_; |
+}; |
+ |
+} // namespace |
class ServiceWorkerWriteToCacheJobTest : public testing::Test { |
public: |
@@ -173,27 +255,24 @@ class ServiceWorkerWriteToCacheJobTest : public testing::Test { |
mock_protocol_handler_(nullptr) {} |
~ServiceWorkerWriteToCacheJobTest() override {} |
- void SetUp() override { |
- helper_.reset( |
- new EmbeddedWorkerTestHelper(base::FilePath(), kMockRenderProcessId)); |
- |
- // A new unstored registration/version. |
- scope_ = GURL("https://host/scope/"); |
- script_url_ = GURL("https://host/script.js"); |
- registration_ = |
- new ServiceWorkerRegistration(scope_, 1L, context()->AsWeakPtr()); |
- version_ = new ServiceWorkerVersion( |
- registration_.get(), script_url_, 1L, context()->AsWeakPtr()); |
- |
- // An empty host. |
+ void CreateHostForVersion( |
+ int process_id, |
+ int provider_id, |
+ const scoped_refptr<ServiceWorkerVersion>& version) { |
scoped_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost( |
- kMockRenderProcessId, MSG_ROUTING_NONE, kMockProviderId, |
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr)); |
- provider_host_ = host->AsWeakPtr(); |
+ process_id, MSG_ROUTING_NONE, provider_id, |
+ SERVICE_WORKER_PROVIDER_FOR_WORKER, context()->AsWeakPtr(), nullptr)); |
+ base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr(); |
context()->AddProviderHost(host.Pass()); |
- provider_host_->running_hosted_version_ = version_; |
+ provider_host->running_hosted_version_ = version; |
+ } |
- context()->storage()->LazyInitialize(base::Bind(&EmptyCallback)); |
+ void SetUpScriptRequest(int process_id, int provider_id) { |
+ request_.reset(); |
+ url_request_context_.reset(); |
+ url_request_job_factory_.reset(); |
+ mock_protocol_handler_ = nullptr; |
+ // URLRequestJobs may post clean-up tasks on destruction. |
base::RunLoop().RunUntilIdle(); |
url_request_context_.reset(new net::URLRequestContext); |
@@ -206,20 +285,38 @@ class ServiceWorkerWriteToCacheJobTest : public testing::Test { |
request_ = url_request_context_->CreateRequest( |
script_url_, net::DEFAULT_PRIORITY, &url_request_delegate_); |
ServiceWorkerRequestHandler::InitializeHandler( |
- request_.get(), |
- context_wrapper(), |
- &blob_storage_context_, |
- kMockRenderProcessId, |
- kMockProviderId, |
- false, |
- FETCH_REQUEST_MODE_NO_CORS, |
- FETCH_CREDENTIALS_MODE_OMIT, |
- RESOURCE_TYPE_SERVICE_WORKER, |
- REQUEST_CONTEXT_TYPE_SERVICE_WORKER, |
- REQUEST_CONTEXT_FRAME_TYPE_NONE, |
+ request_.get(), context_wrapper(), &blob_storage_context_, process_id, |
+ provider_id, false, FETCH_REQUEST_MODE_NO_CORS, |
+ FETCH_CREDENTIALS_MODE_OMIT, RESOURCE_TYPE_SERVICE_WORKER, |
+ REQUEST_CONTEXT_TYPE_SERVICE_WORKER, REQUEST_CONTEXT_FRAME_TYPE_NONE, |
scoped_refptr<ResourceRequestBody>()); |
} |
+ int NextRenderProcessId() { return next_render_process_id_++; } |
+ int NextProviderId() { return next_provider_id_++; } |
+ int NextVersionId() { return next_version_id_++; } |
+ |
+ void SetUp() override { |
+ int render_process_id = NextRenderProcessId(); |
+ int provider_id = NextProviderId(); |
+ helper_.reset( |
+ new EmbeddedWorkerTestHelper(base::FilePath(), render_process_id)); |
+ |
+ // A new unstored registration/version. |
+ scope_ = GURL("https://host/scope/"); |
+ script_url_ = GURL("https://host/script.js"); |
+ registration_ = |
+ new ServiceWorkerRegistration(scope_, 1L, context()->AsWeakPtr()); |
+ version_ = |
+ new ServiceWorkerVersion(registration_.get(), script_url_, |
+ NextVersionId(), context()->AsWeakPtr()); |
+ CreateHostForVersion(render_process_id, provider_id, version_); |
+ SetUpScriptRequest(render_process_id, provider_id); |
+ |
+ context()->storage()->LazyInitialize(base::Bind(&EmptyCallback)); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
void TearDown() override { |
request_.reset(); |
url_request_context_.reset(); |
@@ -232,6 +329,63 @@ class ServiceWorkerWriteToCacheJobTest : public testing::Test { |
base::RunLoop().RunUntilIdle(); |
} |
+ int CreateIncumbent(const std::string& response) { |
+ mock_protocol_handler_->SetCreateJobCallback( |
+ base::Bind(&CreateResponseJob, response)); |
+ request_->Start(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request_->status().status()); |
+ int incumbent_resource_id = |
+ version_->script_cache_map()->LookupResourceId(script_url_); |
+ EXPECT_NE(kInvalidServiceWorkerResponseId, incumbent_resource_id); |
+ |
+ registration_->SetActiveVersion(version_); |
+ |
+ // Teardown the request. |
+ request_.reset(); |
+ url_request_context_.reset(); |
+ url_request_job_factory_.reset(); |
+ mock_protocol_handler_ = nullptr; |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ return incumbent_resource_id; |
+ } |
+ |
+ int GetResourceId(ServiceWorkerVersion* version) { |
+ return version->script_cache_map()->LookupResourceId(script_url_); |
+ } |
+ |
+ // Performs the net request for an update of |registration_|'s incumbent |
+ // to the script |response|. Returns the new version. |
+ scoped_refptr<ServiceWorkerVersion> UpdateScript( |
+ const std::string& response) { |
+ int render_process_id = NextRenderProcessId(); |
+ int provider_id = NextProviderId(); |
+ scoped_refptr<ServiceWorkerVersion> new_version = |
+ new ServiceWorkerVersion(registration_.get(), script_url_, |
+ NextVersionId(), context()->AsWeakPtr()); |
+ CreateHostForVersion(render_process_id, provider_id, new_version); |
+ |
+ SetUpScriptRequest(render_process_id, provider_id); |
+ mock_protocol_handler_->SetCreateJobCallback( |
+ base::Bind(&CreateResponseJob, response)); |
+ request_->Start(); |
+ base::RunLoop().RunUntilIdle(); |
+ return new_version; |
+ } |
+ |
+ void VerifyResource(int id, const std::string& expected) { |
+ ASSERT_NE(kInvalidServiceWorkerResourceId, id); |
+ bool is_equal = false; |
+ scoped_ptr<ServiceWorkerResponseReader> reader = |
+ context()->storage()->CreateResponseReader(id); |
+ scoped_refptr<ResponseVerifier> verifier = new ResponseVerifier( |
+ reader.Pass(), expected, CreateReceiverOnCurrentThread(&is_equal)); |
+ verifier->Start(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_TRUE(is_equal); |
+ } |
+ |
ServiceWorkerContextCore* context() const { return helper_->context(); } |
ServiceWorkerContextWrapper* context_wrapper() const { |
return helper_->context_wrapper(); |
@@ -254,6 +408,10 @@ class ServiceWorkerWriteToCacheJobTest : public testing::Test { |
MockURLRequestDelegate url_request_delegate_; |
GURL scope_; |
GURL script_url_; |
+ |
+ int next_render_process_id_ = 1224; // dummy value |
+ int next_provider_id_ = 1; |
+ int64 next_version_id_ = 1L; |
}; |
TEST_F(ServiceWorkerWriteToCacheJobTest, Normal) { |
@@ -299,4 +457,118 @@ TEST_F(ServiceWorkerWriteToCacheJobTest, CertStatusError) { |
version_->script_cache_map()->LookupResourceId(script_url_)); |
} |
+TEST_F(ServiceWorkerWriteToCacheJobTest, Update_SameScript) { |
+ std::string response = GenerateLongResponse(); |
+ CreateIncumbent(response); |
+ scoped_refptr<ServiceWorkerVersion> version = UpdateScript(response); |
+ EXPECT_EQ(kInvalidServiceWorkerResponseId, GetResourceId(version.get())); |
+} |
+ |
+TEST_F(ServiceWorkerWriteToCacheJobTest, Update_SameSizeScript) { |
+ std::string response = GenerateLongResponse(); |
+ CreateIncumbent(response); |
+ |
+ // Change the first byte. |
+ response[0] = 'x'; |
+ scoped_refptr<ServiceWorkerVersion> version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Change something within the first block. |
+ response[5555] = 'x'; |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Change something in a middle block. |
+ response[kMiddleBlock * kBlockSize + 111] = 'x'; |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Change something within the last block. |
+ response[(kNumBlocks - 1) * kBlockSize] = 'x'; |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Change the last byte. |
+ response[(kNumBlocks * kBlockSize) - 1] = 'x'; |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+} |
+ |
+TEST_F(ServiceWorkerWriteToCacheJobTest, Update_TruncatedScript) { |
+ std::string response = GenerateLongResponse(); |
+ CreateIncumbent(response); |
+ |
+ // Truncate a single byte. |
+ response.resize(response.size() - 1); |
+ scoped_refptr<ServiceWorkerVersion> version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Truncate to a middle block. |
+ response.resize((kMiddleBlock + 1) * kBlockSize + 111); |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Truncate to a block boundary. |
+ response.resize((kMiddleBlock - 1) * kBlockSize); |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Truncate to a single byte. |
+ response.resize(1); |
+ version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+} |
+ |
+TEST_F(ServiceWorkerWriteToCacheJobTest, Update_ElongatedScript) { |
+ std::string original_response = GenerateLongResponse(); |
+ CreateIncumbent(original_response); |
+ |
+ // Extend a single byte. |
+ std::string new_response = original_response + 'a'; |
+ scoped_refptr<ServiceWorkerVersion> version = UpdateScript(new_response); |
+ VerifyResource(GetResourceId(version.get()), new_response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Extend multiple blocks. |
+ new_response = original_response + std::string(3 * kBlockSize, 'a'); |
+ version = UpdateScript(new_response); |
+ VerifyResource(GetResourceId(version.get()), new_response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Extend multiple blocks and bytes. |
+ new_response = original_response + std::string(7 * kBlockSize + 777, 'a'); |
+ version = UpdateScript(new_response); |
+ VerifyResource(GetResourceId(version.get()), new_response); |
+ registration_->SetWaitingVersion(version); |
+} |
+ |
+TEST_F(ServiceWorkerWriteToCacheJobTest, Update_EmptyScript) { |
+ // Create empty incumbent. |
+ CreateIncumbent(std::string()); |
+ |
+ // Update from empty to non-empty. |
+ std::string response = GenerateLongResponse(); |
+ scoped_refptr<ServiceWorkerVersion> version = UpdateScript(response); |
+ VerifyResource(GetResourceId(version.get()), response); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Update from non-empty to empty. |
+ version = UpdateScript(std::string()); |
+ VerifyResource(GetResourceId(version.get()), std::string()); |
+ registration_->SetWaitingVersion(version); |
+ |
+ // Update from empty to empty. |
+ version = UpdateScript(std::string()); |
+ EXPECT_EQ(kInvalidServiceWorkerResponseId, GetResourceId(version.get())); |
+} |
+ |
} // namespace content |