blob: 52492e717853a5275be745983507713c3e7632c4 [file] [log] [blame]
// 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