// 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/40285933): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(data.substr(lower_range));
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(crbug.com/40939899): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(crbug.com/40939899): 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(data.substr(lower_range));
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