| // Copyright 2018 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_single_script_update_checker.h" |
| |
| #include <vector> |
| #include "base/containers/queue.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_storage.h" |
| #include "content/browser/service_worker/service_worker_test_utils.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "net/http/http_util.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| |
| namespace content { |
| namespace { |
| |
| constexpr char kScriptURL[] = "https://example.com/script.js"; |
| constexpr char kSuccessHeader[] = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/javascript\n\n"; |
| |
| class ServiceWorkerSingleScriptUpdateCheckerTest : public testing::Test { |
| public: |
| struct CheckResult { |
| CheckResult( |
| const GURL& script_url, |
| int64_t id, |
| ServiceWorkerSingleScriptUpdateChecker::Result compare_result, |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state) |
| : url(script_url), |
| resource_id(id), |
| result(compare_result), |
| paused_state(std::move(paused_state)) {} |
| |
| CheckResult(CheckResult&& ref) = default; |
| |
| CheckResult& operator=(CheckResult&& ref) = default; |
| |
| ~CheckResult() = default; |
| |
| GURL url; |
| int64_t resource_id; |
| ServiceWorkerSingleScriptUpdateChecker::Result result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state; |
| }; |
| |
| ServiceWorkerSingleScriptUpdateCheckerTest() |
| : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} |
| ~ServiceWorkerSingleScriptUpdateCheckerTest() override = default; |
| |
| ServiceWorkerStorage* storage() { return helper_->context()->storage(); } |
| |
| void SetUp() override { |
| helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()); |
| base::RunLoop run_loop; |
| storage()->LazyInitializeForTest(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| size_t TotalBytes(const std::vector<std::string>& data_chunks) { |
| size_t bytes = 0; |
| for (const auto& data : data_chunks) |
| bytes += data.size(); |
| return bytes; |
| } |
| |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> |
| CreateSingleScriptUpdateChecker( |
| const char* url, |
| std::unique_ptr<ServiceWorkerResponseReader> compare_reader, |
| std::unique_ptr<ServiceWorkerResponseReader> copy_reader, |
| std::unique_ptr<ServiceWorkerResponseWriter> writer, |
| network::TestURLLoaderFactory* loader_factory, |
| base::Optional<CheckResult>* out_check_result) { |
| helper_->SetNetworkFactory(loader_factory); |
| return std::make_unique<ServiceWorkerSingleScriptUpdateChecker>( |
| GURL(url), 0 /* resource_id */, true /* is_main_script */, |
| helper_->url_loader_factory_getter()->GetNetworkFactory(), |
| std::move(compare_reader), std::move(copy_reader), std::move(writer), |
| base::BindOnce( |
| [](base::Optional<CheckResult>* out_check_result_param, |
| const GURL& script_url, int64_t resource_id, |
| ServiceWorkerSingleScriptUpdateChecker::Result result, |
| std::unique_ptr< |
| ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state) { |
| *out_check_result_param = CheckResult( |
| script_url, resource_id, result, std::move(paused_state)); |
| }, |
| out_check_result)); |
| } |
| |
| std::unique_ptr<network::TestURLLoaderFactory> CreateLoaderFactoryWithRespone( |
| const GURL& url, |
| std::string header, |
| std::string body, |
| net::Error error) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| network::ResourceResponseHead head; |
| head.headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(header.c_str(), header.size())); |
| network::URLLoaderCompletionStatus status(error); |
| status.decoded_body_length = body.size(); |
| loader_factory->AddResponse(url, head, body, status); |
| return loader_factory; |
| } |
| |
| protected: |
| TestBrowserThreadBundle thread_bundle_; |
| std::unique_ptr<EmbeddedWorkerTestHelper> helper_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSingleScriptUpdateCheckerTest); |
| }; |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Identical_SingleSyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{body_from_net}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Different_SingleSyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abxx"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Different_MultipleSyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| // The comparison should stop in the second block of data. |
| const std::vector<std::string> body_from_storage{"ab", "cx"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, NetworkDataLong_SyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{"ab", "cd", ""}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, NetworkDataShort_SyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_in_storage{"ab", "cd", "ef", "gh"}; |
| |
| // Stored data that will actually be read from the compare reader. |
| // The last 2 bytes of |body_in_storage| won't be read. |
| const std::vector<std::string> body_read_from_storage{"ab", "cd", "ef"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_read_from_storage, |
| TotalBytes(body_in_storage), false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Identical_SingleAsyncRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{body_from_net}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| true /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker(kScriptURL, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| |
| // Update check stops in WriteHeader() due to the asynchronous read of the |
| // |compare_reader|. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Continue the update check and trigger OnWriteHeadersComplete(). The resumed |
| // update check stops again at CompareData(). |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Continue the update check and trigger OnCompareDataComplete(). This will |
| // finish the entire update check. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_EQ(check_result.value().resource_id, 0); |
| EXPECT_FALSE(check_result.value().paused_state); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| } // namespace |
| } // namespace content |