| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/update_client/background_downloader_mac.h" |
| |
| #include <cstring> |
| #include <memory> |
| #include <string> |
| |
| #include "base/barrier_closure.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/multiprocess_test.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_file_util.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/time/time.h" |
| #include "base/unguessable_token.h" |
| #include "components/update_client/crx_downloader.h" |
| #include "components/update_client/task_traits.h" |
| #include "components/update_client/update_client_errors.h" |
| #include "net/http/http_status_code.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/multiprocess_func_list.h" |
| #include "url/gurl.h" |
| |
| using net::test_server::BasicHttpResponse; |
| using net::test_server::EmbeddedTestServer; |
| using net::test_server::EmbeddedTestServerHandle; |
| using net::test_server::HttpRequest; |
| using net::test_server::HttpResponse; |
| using net::test_server::HttpResponseDelegate; |
| using net::test_server::HungResponse; |
| |
| namespace update_client { |
| namespace { |
| constexpr char kSmallDownloadData[] = "Hello, World!"; |
| constexpr char kDownloadUrlSwitchName[] = "download-url"; |
| constexpr char kDownloadSessionIdSwitchName[] = "download-session-id"; |
| |
| // Returns the lower range from a range header value. |
| int ParseRangeHeader(const std::string& header) { |
| int lower_range = 0; |
| // TODO(crbug.com/1493709): Don't use sscanf. |
| EXPECT_EQ(std::sscanf(header.c_str(), "bytes=%d-", &lower_range), 1); |
| return lower_range; |
| } |
| |
| const std::string GetLargeDownloadData() { |
| return std::string(10000, 'A'); |
| } |
| |
| } // namespace |
| |
| class BackgroundDownloaderTest : public testing::Test { |
| public: |
| void SetUp() override { |
| environment_ = CreateTaskEnvironment(); |
| background_sequence_ = base::ThreadPool::CreateSequencedTaskRunner( |
| kTaskTraitsBackgroundDownloader); |
| |
| shared_session_ = MakeBackgroundDownloaderSharedSession( |
| background_sequence_, download_cache_, |
| base::UnguessableToken::Create().ToString()); |
| |
| downloader_ = base::MakeRefCounted<BackgroundDownloader>( |
| nullptr, shared_session_, background_sequence_); |
| |
| test_server_ = std::make_unique<EmbeddedTestServer>(); |
| test_server_->RegisterRequestHandler(base::BindRepeating( |
| &BackgroundDownloaderTest::HandleRequest, base::Unretained(this))); |
| ASSERT_TRUE((test_server_handle_ = test_server_->StartAndReturnHandle())); |
| } |
| |
| void TearDown() override { |
| background_sequence_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&BackgroundDownloaderSharedSession::InvalidateAndCancel, |
| shared_session_)); |
| } |
| |
| protected: |
| virtual std::unique_ptr<base::test::TaskEnvironment> CreateTaskEnvironment() { |
| return std::make_unique<base::test::TaskEnvironment>(); |
| } |
| |
| void DoStartDownload( |
| const GURL& url, |
| BackgroundDownloaderSharedSession::OnDownloadCompleteCallback |
| on_download_complete_callback) { |
| downloader_->DoStartDownload(url, on_download_complete_callback); |
| } |
| |
| void ExpectSmallDownloadContents(const base::FilePath& location) { |
| std::string contents; |
| EXPECT_TRUE(base::ReadFileToString(location, &contents)); |
| EXPECT_EQ(contents, kSmallDownloadData); |
| } |
| |
| void ExpectLargeDownloadContents(const base::FilePath& location) { |
| std::string contents; |
| EXPECT_TRUE(base::ReadFileToString(location, &contents)); |
| EXPECT_EQ(contents, GetLargeDownloadData()); |
| } |
| |
| void ExpectDownloadMetrics(const CrxDownloader::DownloadMetrics& metrics, |
| int expected_error, |
| int expected_extra_code1, |
| int64_t expected_downloaded_bytes, |
| int64_t expected_total_bytes, |
| bool expect_nonzero_download_time) { |
| EXPECT_EQ(metrics.url, GetURL()); |
| EXPECT_EQ(metrics.downloader, |
| CrxDownloader::DownloadMetrics::kBackgroundMac); |
| EXPECT_EQ(metrics.error, expected_error); |
| EXPECT_EQ(metrics.extra_code1, expected_extra_code1); |
| EXPECT_EQ(metrics.downloaded_bytes, expected_downloaded_bytes); |
| EXPECT_EQ(metrics.total_bytes, expected_total_bytes); |
| if (expect_nonzero_download_time) { |
| EXPECT_NE(metrics.download_time_ms, 0U); |
| } else { |
| EXPECT_EQ(metrics.download_time_ms, 0U); |
| } |
| } |
| |
| GURL GetURL(const std::string& file = "") { |
| const testing::TestInfo* const test_info = |
| testing::UnitTest::GetInstance()->current_test_info(); |
| const std::string path = |
| base::StrCat({test_info->test_suite_name(), "/", file}); |
| GURL::Replacements replacements; |
| replacements.SetPathStr(path); |
| return test_server_->base_url().ReplaceComponents(replacements); |
| } |
| |
| const base::FilePath download_cache_ = |
| base::CreateUniqueTempDirectoryScopedToTest(); |
| scoped_refptr<base::SequencedTaskRunner> background_sequence_; |
| scoped_refptr<BackgroundDownloaderSharedSession> shared_session_; |
| scoped_refptr<BackgroundDownloader> downloader_; |
| base::RepeatingCallback<std::unique_ptr<HttpResponse>( |
| const HttpRequest& request)> |
| request_handler_; |
| std::unique_ptr<base::test::TaskEnvironment> environment_; |
| |
| private: |
| std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { |
| CHECK(request_handler_) << "Request handler not configured for test"; |
| return request_handler_.Run(request); |
| } |
| |
| std::unique_ptr<net::test_server::EmbeddedTestServer> test_server_; |
| EmbeddedTestServerHandle test_server_handle_; |
| }; |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderTest, DISABLED_SimpleDownload) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) { |
| std::unique_ptr<BasicHttpResponse> response = |
| std::make_unique<BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content(kSmallDownloadData); |
| response->set_content_type("text/plain"); |
| return base::WrapUnique<HttpResponse>(response.release()); |
| }); |
| |
| base::RunLoop run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| ExpectSmallDownloadContents(result.response); |
| ExpectDownloadMetrics( |
| metrics, static_cast<int>(CrxDownloaderError::NONE), |
| 0, std::strlen(kSmallDownloadData), |
| std::strlen(kSmallDownloadData), true); |
| }) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderTest, DISABLED_DownloadDiscoveredInCache) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) { |
| EXPECT_TRUE(false) << "The download server was expected to not be reached."; |
| return base::WrapUnique<HttpResponse>(nullptr); |
| }); |
| |
| // Place a download in the cache. |
| ASSERT_TRUE(base::CreateDirectory(download_cache_)); |
| uint32_t url_hash = base::PersistentHash(GetURL().spec()); |
| base::FilePath cached_download_path = download_cache_.AppendASCII( |
| base::HexEncode(reinterpret_cast<uint8_t*>(&url_hash), sizeof(url_hash))); |
| ASSERT_TRUE(base::WriteFile(cached_download_path, kSmallDownloadData)); |
| |
| base::RunLoop run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| ExpectSmallDownloadContents(result.response); |
| ExpectDownloadMetrics( |
| metrics, static_cast<int>(CrxDownloaderError::NONE), |
| 0, std::strlen(kSmallDownloadData), |
| std::strlen(kSmallDownloadData), false); |
| }) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // Sends headers and the first half of the response before invoking `on_reply` |
| // and hanging up. |
| class InterruptedHttpResponse : public HttpResponse { |
| public: |
| explicit InterruptedHttpResponse(base::RepeatingClosure on_reply) |
| : on_reply_(on_reply) {} |
| |
| void SendResponse(base::WeakPtr<HttpResponseDelegate> delegate) override { |
| std::string data = GetLargeDownloadData(); |
| base::StringPairs headers; |
| headers.emplace_back("ETag", "42"); |
| headers.emplace_back("Accept-Ranges", "bytes"); |
| headers.emplace_back("Content-Length", base::NumberToString(data.size())); |
| delegate->SendResponseHeaders( |
| net::HTTP_OK, net::GetHttpReasonPhrase(net::HTTP_OK), headers); |
| delegate->SendContents(std::string(data, data.size() / 2)); |
| on_reply_.Run(); |
| delegate->FinishResponse(); |
| } |
| |
| private: |
| base::RepeatingClosure on_reply_; |
| }; |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| // Tests that the download can resume after the server unexpectedly disconnects. |
| TEST_F(BackgroundDownloaderTest, DISABLED_ServerHangup) { |
| const std::string data = GetLargeDownloadData(); |
| // If the request contains a range request, serve the content as requested. |
| // Otherwise, send the first half of the data before hanging up. |
| request_handler_ = |
| base::BindLambdaForTesting([&](const HttpRequest& request) { |
| if (request.headers.contains("Range")) { |
| EXPECT_EQ(request.headers.at("If-Range"), "42"); |
| int lower_range = ParseRangeHeader(request.headers.at("Range")); |
| |
| std::unique_ptr<BasicHttpResponse> response = |
| std::make_unique<BasicHttpResponse>(); |
| response->set_code(net::HTTP_PARTIAL_CONTENT); |
| response->AddCustomHeader( |
| "Content-Range", |
| base::StringPrintf("bytes %d-%zu/%zu", lower_range, data.size(), |
| data.size())); |
| response->set_content( |
| std::string(data.begin() + lower_range, data.end())); |
| return base::WrapUnique<HttpResponse>(response.release()); |
| } else { |
| return base::WrapUnique<HttpResponse>( |
| new InterruptedHttpResponse(base::DoNothing())); |
| } |
| }); |
| |
| base::RunLoop run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| ExpectLargeDownloadContents(result.response); |
| ExpectDownloadMetrics( |
| metrics, static_cast<int>(CrxDownloaderError::NONE), |
| 0, data.size(), data.size(), true); |
| }) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderTest, DISABLED_DuplicateDownload) { |
| scoped_refptr<base::SequencedTaskRunner> current_task_runner = |
| base::SequencedTaskRunner::GetCurrentDefault(); |
| base::RunLoop second_download_run_loop; |
| request_handler_ = base::BindLambdaForTesting([&](const HttpRequest&) { |
| current_task_runner->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| DoStartDownload( |
| GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_FALSE(is_handled); |
| EXPECT_EQ( |
| result.error, |
| static_cast<int>( |
| CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD)); |
| ExpectDownloadMetrics( |
| metrics, |
| static_cast<int>( |
| CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD), |
| 0, -1, -1, false); |
| }) |
| .Then(second_download_run_loop.QuitClosure())); |
| })); |
| return base::WrapUnique<HttpResponse>(new HungResponse()); |
| }); |
| |
| base::RunLoop first_download_run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| first_download_run_loop.Quit(); |
| })); |
| second_download_run_loop.Run(); |
| shared_session_->InvalidateAndCancel(); |
| first_download_run_loop.Run(); |
| } |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| // Tests that downloads can complete when using multiple instances of |
| // BackgroundDownloader. |
| TEST_F(BackgroundDownloaderTest, DISABLED_ConcurrentDownloaders) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) { |
| std::unique_ptr<BasicHttpResponse> response = |
| std::make_unique<BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content(kSmallDownloadData); |
| response->set_content_type("text/plain"); |
| return base::WrapUnique<HttpResponse>(response.release()); |
| }); |
| |
| scoped_refptr<BackgroundDownloader> other_downloader = |
| base::MakeRefCounted<BackgroundDownloader>(nullptr, shared_session_, |
| background_sequence_); |
| |
| base::RunLoop run_loop; |
| base::RepeatingClosure barrier_closure = |
| base::BarrierClosure(2, run_loop.QuitClosure()); |
| DoStartDownload(GetURL("file1"), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| ExpectSmallDownloadContents(result.response); |
| }) |
| .Then(base::BindPostTaskToCurrentDefault(barrier_closure, |
| FROM_HERE))); |
| other_downloader->DoStartDownload( |
| GetURL("file2"), |
| base::BindLambdaForTesting([&](bool is_handled, |
| const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& |
| metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| ExpectSmallDownloadContents(result.response); |
| }).Then(base::BindPostTaskToCurrentDefault(barrier_closure, FROM_HERE))); |
| |
| run_loop.Run(); |
| } |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderTest, DISABLED_MaxDownloads) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest& request) { |
| return base::WrapUnique<HttpResponse>(new HungResponse()); |
| }); |
| |
| base::RunLoop all_downloads_complete_run_loop; |
| base::RepeatingClosure barrier_closure = |
| base::BarrierClosure(10, all_downloads_complete_run_loop.QuitClosure()); |
| for (int i = 0; i < 10; i++) { |
| DoStartDownload( |
| GetURL(base::NumberToString(i)), |
| base::BindLambdaForTesting( |
| [](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) {}) |
| .Then(base::BindPostTaskToCurrentDefault(barrier_closure))); |
| } |
| |
| base::RunLoop run_loop; |
| // Mac may decide to not make progress on all of the downloads created |
| // immediately. Thus, we have no way of observing when all of the download |
| // tasks above have been created. Delaying the request for the final download |
| // is an alternative to adding intrusive instrumentation to the |
| // implementation. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| DoStartDownload( |
| GetURL(), |
| base::BindLambdaForTesting( |
| [&](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_FALSE(is_handled); |
| EXPECT_EQ( |
| result.error, |
| static_cast<int>( |
| CrxDownloaderError::MAC_BG_SESSION_TOO_MANY_TASKS)); |
| ExpectDownloadMetrics( |
| metrics, |
| static_cast<int>( |
| CrxDownloaderError::MAC_BG_SESSION_TOO_MANY_TASKS), |
| 0, -1, -1, false); |
| }) |
| .Then(run_loop.QuitClosure())); |
| }), |
| base::Milliseconds(100)); |
| run_loop.Run(); |
| |
| shared_session_->InvalidateAndCancel(); |
| all_downloads_complete_run_loop.Run(); |
| } |
| |
| class BackgroundDownloaderPeriodicTasksTest : public BackgroundDownloaderTest { |
| public: |
| std::unique_ptr<base::test::TaskEnvironment> CreateTaskEnvironment() |
| override { |
| // Configure the task environment to use mocked time that starts at the |
| // current real time. This is important for tests which rely on cached file |
| // ages, which are irrespective of mocked time. |
| base::Time now = base::Time::NowFromSystemTime(); |
| auto environment = std::make_unique<base::test::TaskEnvironment>( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME); |
| environment->AdvanceClock(now - base::Time::Now()); |
| return environment; |
| } |
| }; |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderPeriodicTasksTest, DISABLED_CleansStaleDownloads) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) { |
| std::unique_ptr<BasicHttpResponse> response = |
| std::make_unique<BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content(kSmallDownloadData); |
| response->set_content_type("text/plain"); |
| return base::WrapUnique<HttpResponse>(response.release()); |
| }); |
| |
| ASSERT_TRUE(base::WriteFile(download_cache_.AppendASCII("file1"), |
| kSmallDownloadData)); |
| ASSERT_TRUE(base::WriteFile(download_cache_.AppendASCII("file2"), |
| kSmallDownloadData)); |
| environment_->FastForwardBy(base::Days(3)); |
| |
| base::RunLoop run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) {}) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| environment_->FastForwardBy(base::Minutes(30)); |
| environment_->RunUntilIdle(); |
| |
| EXPECT_FALSE(base::PathExists(download_cache_.AppendASCII("file1"))); |
| EXPECT_FALSE(base::PathExists(download_cache_.AppendASCII("file2"))); |
| } |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| TEST_F(BackgroundDownloaderPeriodicTasksTest, |
| DISABLED_CancelsTasksWithNoProgress) { |
| request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) { |
| return base::WrapUnique<HttpResponse>(new HungResponse()); |
| }); |
| |
| base::RunLoop run_loop; |
| DoStartDownload(GetURL(), |
| base::BindLambdaForTesting( |
| [](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_EQ(result.error, -999 /* NSURLErrorCancelled */); |
| }) |
| .Then(run_loop.QuitClosure())); |
| environment_->FastForwardBy(base::Minutes(30)); |
| run_loop.Run(); |
| } |
| |
| class BackgroundDownloaderCrashingClientTest : public testing::Test { |
| public: |
| void SetUp() override { |
| background_sequence_ = base::ThreadPool::CreateSequencedTaskRunner( |
| kTaskTraitsBackgroundDownloader); |
| |
| test_server_ = std::make_unique<EmbeddedTestServer>(); |
| test_server_->RegisterRequestHandler(base::BindRepeating( |
| &BackgroundDownloaderCrashingClientTest::HandleRequest, |
| base::Unretained(this))); |
| ASSERT_TRUE((test_server_handle_ = test_server_->StartAndReturnHandle())); |
| } |
| |
| static void DoStartDownload( |
| scoped_refptr<BackgroundDownloader> downloader, |
| const GURL& url, |
| base::RepeatingCallback<void(bool, |
| const CrxDownloader::Result&, |
| const CrxDownloader::DownloadMetrics&)> |
| on_download_complete_callback) { |
| downloader->DoStartDownload(url, on_download_complete_callback); |
| } |
| |
| protected: |
| GURL GetURL() { |
| const testing::TestInfo* const test_info = |
| testing::UnitTest::GetInstance()->current_test_info(); |
| GURL::Replacements replacements; |
| replacements.SetPathStr(test_info->test_suite_name()); |
| return test_server_->base_url().ReplaceComponents(replacements); |
| } |
| |
| base::RepeatingCallback<std::unique_ptr<HttpResponse>( |
| const HttpRequest& request)> |
| request_handler_; |
| |
| private: |
| std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { |
| CHECK(request_handler_) << "Request handler not configured for test"; |
| return request_handler_.Run(request); |
| } |
| |
| base::test::TaskEnvironment environment_; |
| scoped_refptr<base::SequencedTaskRunner> background_sequence_; |
| std::unique_ptr<net::test_server::EmbeddedTestServer> test_server_; |
| EmbeddedTestServerHandle test_server_handle_; |
| }; |
| |
| // TODO(https://crbug.com/1499017): Disabled due to excessive flakiness. |
| // Test that the download can be recovered after the client process crashes. |
| TEST_F(BackgroundDownloaderCrashingClientTest, DISABLED_ClientCrash) { |
| const std::string data = GetLargeDownloadData(); |
| base::Process test_child_process; |
| // If the request contains a range request, serve the content as requested. |
| // Otherwise, send the first half of the data and hang before terminating the |
| // client process. |
| request_handler_ = |
| base::BindLambdaForTesting([&](const HttpRequest& request) { |
| if (request.headers.contains("Range")) { |
| EXPECT_EQ(request.headers.at("If-Range"), "42"); |
| int lower_range = ParseRangeHeader(request.headers.at("Range")); |
| |
| std::unique_ptr<BasicHttpResponse> response = |
| std::make_unique<BasicHttpResponse>(); |
| response->set_code(net::HTTP_PARTIAL_CONTENT); |
| response->AddCustomHeader( |
| "Content-Range", |
| base::StringPrintf("bytes %d-%zu/%zu", lower_range, data.size(), |
| data.size())); |
| response->set_content( |
| std::string(data.begin() + lower_range, data.end())); |
| return base::WrapUnique<HttpResponse>(response.release()); |
| } else { |
| return base::WrapUnique<HttpResponse>( |
| new InterruptedHttpResponse(base::BindLambdaForTesting([&] { |
| CHECK(test_child_process.IsValid()); |
| // Terminate the child process with extreme prejudice. SIGKILL |
| // is used to prevent the client from cleaning up. |
| kill(test_child_process.Handle(), SIGKILL); |
| }))); |
| } |
| }); |
| |
| base::CommandLine command_line( |
| base::GetMultiProcessTestChildBaseCommandLine()); |
| command_line.AppendSwitchASCII(kDownloadUrlSwitchName, GetURL().spec()); |
| command_line.AppendSwitchASCII(kDownloadSessionIdSwitchName, |
| base::UnguessableToken::Create().ToString()); |
| test_child_process = base::SpawnMultiProcessTestChild( |
| "CrashingDownloadClient", command_line, {}); |
| |
| ASSERT_TRUE(base::WaitForMultiprocessTestChildExit( |
| test_child_process, TestTimeouts::action_timeout(), nullptr)); |
| |
| // Restart the client and expect it to request the remaining content. |
| test_child_process = base::SpawnMultiProcessTestChild( |
| "CrashingDownloadClient", command_line, {}); |
| |
| int exit_code = -1; |
| ASSERT_TRUE(base::WaitForMultiprocessTestChildExit( |
| test_child_process, TestTimeouts::action_timeout(), &exit_code)); |
| EXPECT_EQ(exit_code, 0); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(CrashingDownloadClient) { |
| CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kDownloadUrlSwitchName)); |
| const GURL url(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| kDownloadUrlSwitchName)); |
| CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kDownloadSessionIdSwitchName)); |
| const std::string download_session_id = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| kDownloadSessionIdSwitchName); |
| |
| base::ScopedTempDir download_cache; |
| EXPECT_TRUE(download_cache.CreateUniqueTempDir()); |
| base::test::TaskEnvironment task_environment; |
| scoped_refptr<base::SequencedTaskRunner> background_sequence = |
| base::ThreadPool::CreateSequencedTaskRunner( |
| kTaskTraitsBackgroundDownloader); |
| scoped_refptr<BackgroundDownloaderSharedSession> shared_session = |
| MakeBackgroundDownloaderSharedSession( |
| background_sequence, download_cache.GetPath(), download_session_id); |
| scoped_refptr<BackgroundDownloader> downloader = |
| base::MakeRefCounted<BackgroundDownloader>(nullptr, shared_session, |
| background_sequence); |
| |
| base::RunLoop run_loop; |
| BackgroundDownloaderCrashingClientTest::DoStartDownload( |
| downloader, url, |
| base::BindLambdaForTesting( |
| [](bool is_handled, const CrxDownloader::Result& result, |
| const CrxDownloader::DownloadMetrics& metrics) { |
| EXPECT_TRUE(is_handled); |
| EXPECT_EQ(result.error, 0); |
| EXPECT_TRUE(base::PathExists(result.response)); |
| }) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| return 0; |
| } |
| |
| } // namespace update_client |