| // Copyright 2017 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 "chrome/browser/safe_browsing/chrome_cleaner/mock_chrome_cleaner_process_win.h" |
| |
| #include <stdlib.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread.h" |
| #include "base/values.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/win_util.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/chrome_cleaner/public/constants/constants.h" |
| #include "components/chrome_cleaner/public/interfaces/chrome_prompt.mojom.h" |
| #include "components/chrome_cleaner/public/proto/chrome_prompt.pb.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest.h" |
| #include "mojo/core/embedder/embedder.h" |
| #include "mojo/core/embedder/scoped_ipc_support.h" |
| #include "mojo/public/cpp/platform/platform_channel.h" |
| #include "mojo/public/cpp/system/invitation.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace safe_browsing { |
| |
| namespace { |
| |
| using ::chrome_cleaner::mojom::ChromePromptPtr; |
| using ::chrome_cleaner::mojom::ChromePromptPtrInfo; |
| using mojo::core::ScopedIPCSupport; |
| using CrashPoint = MockChromeCleanerProcess::CrashPoint; |
| using ItemsReporting = MockChromeCleanerProcess::ItemsReporting; |
| using PromptAcceptance = ChromePromptActions::PromptAcceptance; |
| using UwsFoundStatus = MockChromeCleanerProcess::UwsFoundStatus; |
| |
| constexpr char kCrashPointSwitch[] = "mock-crash-point"; |
| constexpr char kUwsFoundSwitch[] = "mock-uws-found"; |
| constexpr char kRebootRequiredSwitch[] = "mock-reboot-required"; |
| constexpr char kRegistryKeysReportingSwitch[] = "registry-keys-reporting"; |
| constexpr char kExtensionsReportingSwitch[] = "extensions-reporting"; |
| constexpr char kExpectedUserResponseSwitch[] = "mock-expected-user-response"; |
| |
| // MockCleanerResults |
| |
| class MockCleanerResults { |
| public: |
| explicit MockCleanerResults(const MockChromeCleanerProcess::Options& options) |
| : options_(options) {} |
| |
| virtual ~MockCleanerResults() = default; |
| |
| virtual void SendScanResults(base::OnceClosure done_closure) = 0; |
| |
| PromptAcceptance received_prompt_acceptance() const { |
| return received_prompt_acceptance_; |
| } |
| |
| void ReceivePromptAcceptance(base::OnceClosure done_closure, |
| PromptAcceptance acceptance) { |
| received_prompt_acceptance_ = acceptance; |
| if (options_.crash_point() == CrashPoint::kAfterResponseReceived) |
| ::exit(MockChromeCleanerProcess::kDeliberateCrashExitCode); |
| std::move(done_closure).Run(); |
| } |
| |
| protected: |
| MockChromeCleanerProcess::Options options_; |
| PromptAcceptance received_prompt_acceptance_ = PromptAcceptance::UNSPECIFIED; |
| |
| private: |
| MockCleanerResults(const MockCleanerResults& other) = delete; |
| MockCleanerResults& operator=(const MockCleanerResults& other) = delete; |
| }; |
| |
| // MockCleanerResultsMojo |
| |
| class MockCleanerResultsMojo : public MockCleanerResults { |
| public: |
| // Sets up Mojo IPC support using |io_task_runner| to process messages. |
| // Connects to the IPC pipe given on |command_line|. |
| MockCleanerResultsMojo( |
| const MockChromeCleanerProcess::Options& options, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner, |
| const base::CommandLine& command_line) |
| : MockCleanerResults(options), io_task_runner_(io_task_runner) { |
| mojo::core::Init(); |
| scoped_ipc_support_ = std::make_unique<ScopedIPCSupport>( |
| io_task_runner, ScopedIPCSupport::ShutdownPolicy::CLEAN); |
| |
| auto invitation = mojo::IncomingInvitation::Accept( |
| mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine( |
| command_line)); |
| const std::string pipe_token = command_line.GetSwitchValueASCII( |
| chrome_cleaner::kChromeMojoPipeTokenSwitch); |
| ChromePromptPtrInfo prompt_ptr_info( |
| invitation.ExtractMessagePipe(pipe_token), 0); |
| |
| // Mojo requires that the ChromePromptPtr is bound on the IO sequence. |
| io_task_runner->PostTask( |
| FROM_HERE, base::BindOnce(&ChromePromptPtr::Bind, |
| base::Unretained(chrome_prompt_ptr_.get()), |
| std::move(prompt_ptr_info), nullptr)); |
| } |
| |
| ~MockCleanerResultsMojo() override { |
| // Mojo requires that the ChromePromptPtr is deleted on the IO sequence. |
| // Do not shut down Mojo until after ChromePromptPtr is deleted. |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](std::unique_ptr<ChromePromptPtr> chrome_prompt_ptr, |
| std::unique_ptr<ScopedIPCSupport> scoped_ipc_support) { |
| chrome_prompt_ptr.release(); |
| scoped_ipc_support.release(); |
| }, |
| std::move(chrome_prompt_ptr_), std::move(scoped_ipc_support_))); |
| } |
| |
| void SendScanResults(base::OnceClosure done_closure) override { |
| if (options_.crash_point() == CrashPoint::kAfterRequestSent) { |
| // This task is posted to the IPC thread so that it will happen after the |
| // request is sent to the parent process and before the response gets |
| // handled on the IPC thread. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce([]() { |
| ::exit(MockChromeCleanerProcess::kDeliberateCrashExitCode); |
| })); |
| } |
| |
| (*chrome_prompt_ptr_) |
| ->PromptUser( |
| options_.files_to_delete(), options_.registry_keys(), |
| options_.extension_ids(), |
| base::BindOnce(&MockCleanerResultsMojo::ReceivePromptUserResponse, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void ReceivePromptUserResponse( |
| base::OnceClosure done_closure, |
| chrome_cleaner::mojom::PromptAcceptance acceptance) { |
| ReceivePromptAcceptance(std::move(done_closure), |
| static_cast<PromptAcceptance>(acceptance)); |
| } |
| |
| private: |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner_; |
| std::unique_ptr<ChromePromptPtr> chrome_prompt_ptr_ = |
| std::make_unique<ChromePromptPtr>(); |
| std::unique_ptr<ScopedIPCSupport> scoped_ipc_support_; |
| }; |
| |
| // MockCleanerResultsProtobuf |
| |
| class MockCleanerResultsProtobuf : public MockCleanerResults { |
| public: |
| MockCleanerResultsProtobuf(const MockChromeCleanerProcess::Options& options, |
| const base::CommandLine& command_line) |
| : MockCleanerResults(options) { |
| uint32_t handle_value; |
| if (base::StringToUint(command_line.GetSwitchValueNative( |
| chrome_cleaner::kChromeReadHandleSwitch), |
| &handle_value)) { |
| read_handle_.Set(base::win::Uint32ToHandle(handle_value)); |
| } |
| if (base::StringToUint(command_line.GetSwitchValueNative( |
| chrome_cleaner::kChromeWriteHandleSwitch), |
| &handle_value)) { |
| write_handle_.Set(base::win::Uint32ToHandle(handle_value)); |
| } |
| } |
| |
| ~MockCleanerResultsProtobuf() override = default; |
| |
| void SendScanResults(base::OnceClosure done_closure) override { |
| base::ScopedClosureRunner call_done_closure(std::move(done_closure)); |
| if (!read_handle_.IsValid() || !write_handle_.IsValid()) { |
| LOG(ERROR) << "IPC pipes were not connected correctly"; |
| return; |
| } |
| // TODO(crbug.com/969139): Populate a request proto based on |options_| and |
| // send it. |
| |
| // Send the protocol version number. |
| DWORD bytes_written = 0; |
| static const uint8_t kVersion = 1; |
| if (!::WriteFile(write_handle_.Get(), &kVersion, sizeof(kVersion), |
| &bytes_written, nullptr)) { |
| PLOG(ERROR) << "Error writing protocol version"; |
| return; |
| } |
| |
| // Send a PromptUser request. |
| chrome_cleaner::ChromePromptRequest request; |
| chrome_cleaner::PromptUserRequest* prompt_user = |
| request.mutable_prompt_user(); |
| for (const base::FilePath& file : options_.files_to_delete()) { |
| prompt_user->add_files_to_delete(file.AsUTF8Unsafe()); |
| } |
| if (options_.registry_keys().has_value()) { |
| for (const base::string16& key : options_.registry_keys().value()) { |
| prompt_user->add_registry_keys(base::UTF16ToUTF8(key)); |
| } |
| } |
| if (options_.extension_ids().has_value()) { |
| for (const base::string16& id : options_.extension_ids().value()) { |
| prompt_user->add_extension_ids(base::UTF16ToUTF8(id)); |
| } |
| } |
| if (!WriteMessage(request.SerializeAsString())) |
| return; |
| |
| if (options_.crash_point() == CrashPoint::kAfterRequestSent) { |
| ::exit(MockChromeCleanerProcess::kDeliberateCrashExitCode); |
| } |
| |
| // Wait for the response. |
| std::string response_message = ReadResponse(); |
| if (response_message.empty()) |
| return; |
| chrome_cleaner::PromptUserResponse response; |
| if (!response.ParseFromString(response_message)) { |
| LOG(ERROR) << "Read invalid PromptUser response: " << response_message; |
| return; |
| } |
| ReceivePromptAcceptance( |
| base::BindOnce(&MockCleanerResultsProtobuf::SendCloseConnectionRequest, |
| base::Unretained(this), call_done_closure.Release()), |
| static_cast<PromptAcceptance>(response.prompt_acceptance())); |
| } |
| |
| void SendCloseConnectionRequest(base::OnceClosure done_closure) { |
| chrome_cleaner::ChromePromptRequest request; |
| // Initialize a CloseConnectionRequest |
| request.mutable_close_connection(); |
| WriteMessage(request.SerializeAsString()); |
| std::move(done_closure).Run(); |
| } |
| |
| private: |
| bool WriteMessage(const std::string& message) { |
| uint32_t message_length = message.size(); |
| DWORD bytes_written = 0; |
| if (!::WriteFile(write_handle_.Get(), &message_length, |
| sizeof(message_length), &bytes_written, nullptr)) { |
| PLOG(ERROR) << "Error writing message length"; |
| return false; |
| } |
| if (!::WriteFile(write_handle_.Get(), message.c_str(), message_length, |
| &bytes_written, nullptr)) { |
| PLOG(ERROR) << "Error writing message"; |
| return false; |
| } |
| return true; |
| } |
| |
| std::string ReadResponse() { |
| uint32_t response_length = 0; |
| DWORD bytes_read = 0; |
| // Include space for the null terminator in the WriteInto call. |
| if (!::ReadFile(read_handle_.Get(), &response_length, |
| sizeof(response_length), &bytes_read, nullptr)) { |
| PLOG(ERROR) << "Error reading response length"; |
| return std::string(); |
| } |
| std::string response_message; |
| if (!::ReadFile(read_handle_.Get(), |
| base::WriteInto(&response_message, response_length + 1), |
| response_length, &bytes_read, nullptr)) { |
| PLOG(ERROR) << "Error reading response message"; |
| return std::string(); |
| } |
| return response_message; |
| } |
| |
| base::win::ScopedHandle read_handle_; |
| base::win::ScopedHandle write_handle_; |
| }; |
| |
| scoped_refptr<extensions::Extension> CreateExtension(const base::string16& name, |
| const base::string16& id, |
| std::string* error) { |
| base::DictionaryValue manifest; |
| manifest.SetKey("name", base::Value(name)); |
| manifest.SetKey("version", base::Value("0")); |
| manifest.SetKey("manifest_version", base::Value(2)); |
| |
| return extensions::Extension::Create(base::FilePath(), |
| extensions::Manifest::INTERNAL, manifest, |
| 0, base::UTF16ToUTF8(id), error); |
| } |
| |
| } // namespace |
| |
| const base::char16 MockChromeCleanerProcess::kInstalledExtensionId1[] = |
| L"aaaabbbbccccddddeeeeffffgggghhhh"; |
| const base::char16 MockChromeCleanerProcess::kInstalledExtensionName1[] = |
| L"Some Extension"; |
| const base::char16 MockChromeCleanerProcess::kInstalledExtensionId2[] = |
| L"ababababcdcdcdcdefefefefghghghgh"; |
| const base::char16 MockChromeCleanerProcess::kInstalledExtensionName2[] = |
| L"Another Extension"; |
| const base::char16 MockChromeCleanerProcess::kUnknownExtensionId[] = |
| L"unexpectedextensionidabcdefghijk"; |
| |
| // static |
| void MockChromeCleanerProcess::AddMockExtensionsToProfile(Profile* profile) { |
| extensions::ExtensionRegistry* extension_registry = |
| extensions::ExtensionRegistry::Get(profile); |
| scoped_refptr<extensions::Extension> extension; |
| std::string error; |
| |
| extension = |
| CreateExtension(MockChromeCleanerProcess::kInstalledExtensionName1, |
| MockChromeCleanerProcess::kInstalledExtensionId1, &error); |
| if (extension && error.empty()) { |
| extension_registry->AddEnabled(extension); |
| } else { |
| LOG(ERROR) << "Error creating mock extension: " << error; |
| } |
| |
| extension = |
| CreateExtension(MockChromeCleanerProcess::kInstalledExtensionName2, |
| MockChromeCleanerProcess::kInstalledExtensionId2, &error); |
| if (extension && error.empty()) { |
| extension_registry->AddEnabled(extension); |
| } else { |
| LOG(ERROR) << "Error creating mock extension: " << error; |
| } |
| } |
| |
| // static |
| bool MockChromeCleanerProcess::Options::FromCommandLine( |
| const base::CommandLine& command_line, |
| Options* options) { |
| int registry_keys_reporting_int = -1; |
| if (!base::StringToInt( |
| command_line.GetSwitchValueASCII(kRegistryKeysReportingSwitch), |
| ®istry_keys_reporting_int) || |
| registry_keys_reporting_int < 0 || |
| registry_keys_reporting_int >= |
| static_cast<int>(ItemsReporting::kNumItemsReporting)) { |
| return false; |
| } |
| |
| int extensions_reporting_int = -1; |
| if (!base::StringToInt( |
| command_line.GetSwitchValueASCII(kExtensionsReportingSwitch), |
| &extensions_reporting_int) || |
| extensions_reporting_int < 0 || |
| extensions_reporting_int >= |
| static_cast<int>(ItemsReporting::kNumItemsReporting)) { |
| return false; |
| } |
| |
| options->SetReportedResults( |
| command_line.HasSwitch(kUwsFoundSwitch), |
| static_cast<ItemsReporting>(registry_keys_reporting_int), |
| static_cast<ItemsReporting>(extensions_reporting_int)); |
| options->set_reboot_required(command_line.HasSwitch(kRebootRequiredSwitch)); |
| |
| if (command_line.HasSwitch(kCrashPointSwitch)) { |
| int crash_point_int = 0; |
| if (base::StringToInt(command_line.GetSwitchValueASCII(kCrashPointSwitch), |
| &crash_point_int) && |
| crash_point_int >= 0 && |
| crash_point_int < static_cast<int>(CrashPoint::kNumCrashPoints)) { |
| options->set_crash_point(static_cast<CrashPoint>(crash_point_int)); |
| } else { |
| return false; |
| } |
| } |
| |
| if (command_line.HasSwitch(kExpectedUserResponseSwitch)) { |
| static const std::vector<PromptAcceptance> kValidPromptAcceptanceList{ |
| PromptAcceptance::UNSPECIFIED, |
| PromptAcceptance::ACCEPTED_WITH_LOGS, |
| PromptAcceptance::ACCEPTED_WITHOUT_LOGS, |
| PromptAcceptance::DENIED, |
| }; |
| |
| int expected_response_int = 0; |
| if (!base::StringToInt( |
| command_line.GetSwitchValueASCII(kExpectedUserResponseSwitch), |
| &expected_response_int)) { |
| return false; |
| } |
| |
| const PromptAcceptance expected_response = |
| static_cast<PromptAcceptance>(expected_response_int); |
| if (!base::Contains(kValidPromptAcceptanceList, expected_response)) { |
| return false; |
| } |
| |
| options->set_expected_user_response(expected_response); |
| } |
| |
| return true; |
| } |
| |
| MockChromeCleanerProcess::Options::Options() = default; |
| |
| MockChromeCleanerProcess::Options::Options(const Options& other) |
| : files_to_delete_(other.files_to_delete_), |
| registry_keys_(other.registry_keys_), |
| extension_ids_(other.extension_ids_), |
| reboot_required_(other.reboot_required_), |
| crash_point_(other.crash_point_), |
| registry_keys_reporting_(other.registry_keys_reporting_), |
| extensions_reporting_(other.extensions_reporting_), |
| expected_user_response_(other.expected_user_response_) {} |
| |
| MockChromeCleanerProcess::Options& MockChromeCleanerProcess::Options::operator=( |
| const Options& other) { |
| files_to_delete_ = other.files_to_delete_; |
| registry_keys_ = other.registry_keys_; |
| extension_ids_ = other.extension_ids_; |
| reboot_required_ = other.reboot_required_; |
| crash_point_ = other.crash_point_; |
| registry_keys_reporting_ = other.registry_keys_reporting_; |
| extensions_reporting_ = other.extensions_reporting_; |
| expected_user_response_ = other.expected_user_response_; |
| return *this; |
| } |
| |
| MockChromeCleanerProcess::Options::~Options() {} |
| |
| void MockChromeCleanerProcess::Options::AddSwitchesToCommandLine( |
| base::CommandLine* command_line) const { |
| if (!files_to_delete_.empty()) |
| command_line->AppendSwitch(kUwsFoundSwitch); |
| |
| if (reboot_required()) |
| command_line->AppendSwitch(kRebootRequiredSwitch); |
| |
| if (crash_point() != CrashPoint::kNone) { |
| command_line->AppendSwitchASCII( |
| kCrashPointSwitch, |
| base::NumberToString(static_cast<int>(crash_point()))); |
| } |
| |
| command_line->AppendSwitchASCII( |
| kRegistryKeysReportingSwitch, |
| base::NumberToString(static_cast<int>(registry_keys_reporting()))); |
| command_line->AppendSwitchASCII( |
| kExtensionsReportingSwitch, |
| base::NumberToString(static_cast<int>(extensions_reporting()))); |
| |
| if (expected_user_response() != PromptAcceptance::UNSPECIFIED) { |
| command_line->AppendSwitchASCII( |
| kExpectedUserResponseSwitch, |
| base::NumberToString(static_cast<int>(expected_user_response()))); |
| } |
| } |
| |
| void MockChromeCleanerProcess::Options::SetReportedResults( |
| bool has_files_to_remove, |
| ItemsReporting registry_keys_reporting, |
| ItemsReporting extensions_reporting) { |
| if (!has_files_to_remove) |
| files_to_delete_.clear(); |
| if (has_files_to_remove) { |
| files_to_delete_.push_back( |
| base::FilePath(FILE_PATH_LITERAL("/path/to/file1.exe"))); |
| files_to_delete_.push_back( |
| base::FilePath(FILE_PATH_LITERAL("/path/to/other/file2.exe"))); |
| files_to_delete_.push_back( |
| base::FilePath(FILE_PATH_LITERAL("/path/to/some file.dll"))); |
| } |
| |
| registry_keys_reporting_ = registry_keys_reporting; |
| switch (registry_keys_reporting) { |
| case ItemsReporting::kUnsupported: |
| // Defined as an optional object in which a registry keys vector is not |
| // present. |
| registry_keys_ = base::Optional<std::vector<base::string16>>(); |
| break; |
| |
| case ItemsReporting::kNotReported: |
| // Defined as an optional object in which an empty registry keys vector is |
| // present. |
| registry_keys_ = |
| base::Optional<std::vector<base::string16>>(base::in_place); |
| break; |
| |
| case ItemsReporting::kReported: |
| // Defined as an optional object in which a non-empty registry keys vector |
| // is present. |
| registry_keys_ = base::Optional<std::vector<base::string16>>({ |
| L"HKCU:32\\Software\\Some\\Unwanted Software", |
| L"HKCU:32\\Software\\Another\\Unwanted Software", |
| }); |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| |
| extensions_reporting_ = extensions_reporting; |
| switch (extensions_reporting) { |
| case ItemsReporting::kUnsupported: |
| // Defined as an optional object in which an extensions vector is not |
| // present. |
| extension_ids_ = base::Optional<std::vector<base::string16>>(); |
| expected_extension_names_ = base::Optional<std::vector<base::string16>>(); |
| break; |
| |
| case ItemsReporting::kNotReported: |
| // Defined as an optional object in which an empty extensions vector is |
| // present. |
| extension_ids_ = |
| base::Optional<std::vector<base::string16>>(base::in_place); |
| expected_extension_names_ = |
| base::Optional<std::vector<base::string16>>(base::in_place); |
| break; |
| |
| case ItemsReporting::kReported: |
| // Defined as an optional object in which a non-empty extensions vector is |
| // present. |
| extension_ids_ = base::Optional<std::vector<base::string16>>({ |
| kInstalledExtensionId1, kInstalledExtensionId2, kUnknownExtensionId, |
| }); |
| // Scanner results only fetches extension names on Windows Chrome build. |
| #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) |
| expected_extension_names_ = base::Optional<std::vector<base::string16>>({ |
| kInstalledExtensionName1, kInstalledExtensionName2, |
| l10n_util::GetStringFUTF16( |
| IDS_SETTINGS_RESET_CLEANUP_DETAILS_EXTENSION_UNKNOWN, |
| kUnknownExtensionId), |
| }); |
| #else |
| expected_extension_names_ = |
| base::Optional<std::vector<base::string16>>(base::in_place); |
| #endif |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| int MockChromeCleanerProcess::Options::ExpectedExitCode( |
| PromptAcceptance received_prompt_acceptance) const { |
| if (crash_point() != CrashPoint::kNone) |
| return kDeliberateCrashExitCode; |
| |
| if (files_to_delete_.empty()) |
| return kNothingFoundExitCode; |
| |
| if (received_prompt_acceptance == PromptAcceptance::ACCEPTED_WITH_LOGS || |
| received_prompt_acceptance == PromptAcceptance::ACCEPTED_WITHOUT_LOGS) { |
| return reboot_required() ? kRebootRequiredExitCode |
| : kRebootNotRequiredExitCode; |
| } |
| |
| return kDeclinedExitCode; |
| } |
| |
| MockChromeCleanerProcess::MockChromeCleanerProcess() = default; |
| |
| MockChromeCleanerProcess::~MockChromeCleanerProcess() = default; |
| |
| bool MockChromeCleanerProcess::InitWithCommandLine( |
| const base::CommandLine& command_line) { |
| command_line_ = std::make_unique<base::CommandLine>(command_line); |
| if (!Options::FromCommandLine(command_line, &options_)) |
| return false; |
| return true; |
| } |
| |
| int MockChromeCleanerProcess::Run() { |
| // We use EXPECT_*() macros to get good log lines, but since this code is run |
| // in a separate process, failing a check in an EXPECT_*() macro will not fail |
| // the test. Therefore, we use ::testing::Test::HasFailure() to detect |
| // EXPECT_*() failures and return an error code that indicates that the test |
| // should fail. |
| if (options_.crash_point() == CrashPoint::kOnStartup) |
| exit(kDeliberateCrashExitCode); |
| |
| base::Thread::Options thread_options(base::MessagePumpType::IO, 0); |
| base::Thread io_thread("IPCThread"); |
| EXPECT_TRUE(io_thread.StartWithOptions(thread_options)); |
| if (::testing::Test::HasFailure()) |
| return kInternalTestFailureExitCode; |
| |
| std::unique_ptr<MockCleanerResults> mock_results; |
| |
| if (command_line_->HasSwitch(chrome_cleaner::kChromeMojoPipeTokenSwitch)) { |
| mock_results = std::make_unique<MockCleanerResultsMojo>( |
| options_, io_thread.task_runner(), *command_line_); |
| } else { |
| mock_results = |
| std::make_unique<MockCleanerResultsProtobuf>(options_, *command_line_); |
| } |
| |
| if (options_.crash_point() == CrashPoint::kAfterConnection) |
| exit(kDeliberateCrashExitCode); |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| base::RunLoop run_loop; |
| // After the response from the parent process is received, this will post a |
| // task to unblock the child process's main thread. |
| auto quit_closure = base::BindOnce( |
| [](scoped_refptr<base::SequencedTaskRunner> main_runner, |
| base::Closure quit_closure) { |
| main_runner->PostTask(FROM_HERE, std::move(quit_closure)); |
| }, |
| base::SequencedTaskRunnerHandle::Get(), |
| base::Passed(run_loop.QuitClosure())); |
| |
| io_thread.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCleanerResults::SendScanResults, |
| base::Unretained(mock_results.get()), |
| base::Passed(&quit_closure))); |
| |
| run_loop.Run(); |
| |
| EXPECT_NE(mock_results->received_prompt_acceptance(), |
| PromptAcceptance::UNSPECIFIED); |
| EXPECT_EQ(mock_results->received_prompt_acceptance(), |
| options_.expected_user_response()); |
| if (::testing::Test::HasFailure()) |
| return kInternalTestFailureExitCode; |
| return options_.ExpectedExitCode(mock_results->received_prompt_acceptance()); |
| } |
| |
| // Keep the printable names of these enums short since they're used in tests |
| // with very long parameter lists. |
| |
| std::ostream& operator<<(std::ostream& out, CrashPoint crash_point) { |
| return out << "CrPt" << static_cast<int>(crash_point); |
| } |
| |
| std::ostream& operator<<(std::ostream& out, UwsFoundStatus status) { |
| return out << "UwS" << static_cast<int>(status); |
| } |
| |
| std::ostream& operator<<( |
| std::ostream& out, |
| MockChromeCleanerProcess::ExtensionCleaningFeatureStatus status) { |
| return out << "Ext" << static_cast<int>(status); |
| } |
| |
| std::ostream& operator<<( |
| std::ostream& out, |
| MockChromeCleanerProcess::ProtobufIPCFeatureStatus status) { |
| return out << "Ipc" << static_cast<int>(status); |
| } |
| |
| std::ostream& operator<<(std::ostream& out, ItemsReporting items_reporting) { |
| return out << "Items" << static_cast<int>(items_reporting); |
| } |
| |
| } // namespace safe_browsing |