// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/installer/setup/downgrade_cleanup.h"
#include "base/command_line.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_reg_util_win.h"
#include "base/version.h"
#include "base/win/registry.h"
#include "base/win/windows_types.h"
#include "chrome/install_static/install_util.h"
#include "chrome/install_static/test/scoped_install_details.h"
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/work_item.h"
#include "chrome/installer/util/work_item_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace installer {
namespace {
constexpr wchar_t kVersion[] = L"80.0.0.0";
constexpr wchar_t kLastBreakingVersion[] = L"81.0.0.0";
constexpr char kDowngradeCleanupSuccessMain[] = "DowngradeCleanupSuccessMain";
constexpr wchar_t kDummyKey1[] = L"dummy1";
constexpr wchar_t kDummyKey2[] = L"dummy2";
constexpr int64_t kDummyValue1 = 2019;
constexpr int64_t kDummyValue2 = 2020;
} // namespace
// Test fixture to verify that `AddDowngradeCleanupItems` launches the right
// processes with the required placeholder replacements. This mimics the command
// line returned by `GetDowngradeCleanupCommandWithPlaceholders`, but the
// process launched is a test process in which the command line arguments are
// verified
class DowngradeCleanupTest : public base::MultiProcessTest {
protected:
DowngradeCleanupTest() = default;
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(reg_root()));
}
// Returns a commande line with the placeholders required by
// `GetDowngradeCleanupCommandWithPlaceholders`.
base::CommandLine MakeCmdLine(const std::string& procname) override {
base::CommandLine command_line =
base::MultiProcessTest::MakeCmdLine(procname);
command_line.AppendSwitchNative(switches::kCleanupForDowngradeVersion,
L"$1");
command_line.AppendSwitchNative(switches::kCleanupForDowngradeOperation,
L"$2");
return command_line;
}
const wchar_t* client_state_key() const { return client_state_key_.c_str(); }
// Adds a dummy work item before and after the cleanup work items. If
// `expect_cleanup_items_added` is true, `AddDowngradeCleanupItems` must have
// added some work items in `list`, otherwise it should not have added any
// work items.
void AddCleanupWorkItems(WorkItemList* list,
bool expect_cleanup_items_added) {
list->AddSetRegValueWorkItem(reg_root(), client_state_key(),
KEY_WOW64_32KEY, kDummyKey1, kDummyValue1,
false);
ASSERT_EQ(AddDowngradeCleanupItems(
base::Version(base::WideToASCII(kVersion)), list),
expect_cleanup_items_added);
list->AddSetRegValueWorkItem(reg_root(), client_state_key(),
KEY_WOW64_32KEY, kDummyKey2, kDummyValue2,
false);
}
void ExpectPreCleanupWorkItems(bool applied) {
int64_t value = 0;
base::win::RegKey(reg_root(), client_state_key(),
KEY_READ | KEY_WOW64_32KEY)
.ReadInt64(kDummyKey1, &value);
EXPECT_EQ(value, (applied ? kDummyValue1 : 0));
}
void ExpectPostCleanupWorkItems(bool applied) {
int64_t expected_value = 0;
base::win::RegKey(reg_root(), client_state_key(),
KEY_READ | KEY_WOW64_32KEY)
.ReadInt64(kDummyKey2, &expected_value);
EXPECT_EQ(expected_value, applied ? kDummyValue2 : 0);
}
static HKEY reg_root() {
return install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER;
}
private:
install_static::ScopedInstallDetails install_details_{true};
registry_util::RegistryOverrideManager registry_override_manager_;
const std::wstring client_state_key_ =
install_static::GetClientStateKeyPath();
};
MULTIPROCESS_TEST_MAIN(DowngradeCleanupSuccessMain) {
install_static::ScopedInstallDetails install_details(/*system_level=*/true);
base::CommandLine* const command_line =
base::CommandLine::ForCurrentProcess();
auto version =
command_line->GetSwitchValueNative(switches::kCleanupForDowngradeVersion);
auto operation = command_line->GetSwitchValueNative(
switches::kCleanupForDowngradeOperation);
auto should_fail = command_line->HasSwitch("fail");
installer::InstallStatus result_code = DOWNGRADE_CLEANUP_UNKNOWN_OPERATION;
EXPECT_EQ(version, kVersion);
if (operation == L"cleanup") {
result_code = should_fail ? installer::DOWNGRADE_CLEANUP_FAILED
: installer::DOWNGRADE_CLEANUP_SUCCESS;
} else if (operation == L"revert") {
result_code = installer::UNDO_DOWNGRADE_CLEANUP_SUCCESS;
} else {
NOTREACHED_IN_MIGRATION();
}
return ::testing::Test::HasFailure() ? -1 : result_code;
}
TEST_F(DowngradeCleanupTest, SuccessfulCleanup) {
std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(google_update::kRegDowngradeCleanupCommandField,
MakeCmdLine(kDowngradeCleanupSuccessMain)
.GetCommandLineString()
.c_str()),
ERROR_SUCCESS);
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(
google_update::kRegCleanInstallRequiredForVersionBelowField,
kLastBreakingVersion),
ERROR_SUCCESS);
ASSERT_NO_FATAL_FAILURE(
AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/true));
ASSERT_TRUE(list->Do());
ExpectPreCleanupWorkItems(/*applied=*/true);
ExpectPostCleanupWorkItems(/*applied=*/true);
list->Rollback();
ExpectPreCleanupWorkItems(/*applied=*/false);
ExpectPostCleanupWorkItems(/*applied=*/false);
}
TEST_F(DowngradeCleanupTest, FailedCleanup) {
std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
auto cmd = MakeCmdLine(kDowngradeCleanupSuccessMain);
cmd.AppendSwitch("fail");
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(google_update::kRegDowngradeCleanupCommandField,
cmd.GetCommandLineString().c_str()),
ERROR_SUCCESS);
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(
google_update::kRegCleanInstallRequiredForVersionBelowField,
kLastBreakingVersion),
ERROR_SUCCESS);
ASSERT_NO_FATAL_FAILURE(
AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/true));
EXPECT_FALSE(list->Do());
ExpectPreCleanupWorkItems(/*applied=*/true);
ExpectPostCleanupWorkItems(/*applied=*/false);
list->Rollback();
ExpectPreCleanupWorkItems(/*applied=*/false);
ExpectPostCleanupWorkItems(/*applied=*/false);
}
TEST_F(DowngradeCleanupTest, MissingArguments) {
std::unique_ptr<WorkItemList> list;
list.reset(WorkItem::CreateWorkItemList());
auto cmd = MakeCmdLine(kDowngradeCleanupSuccessMain);
// Currently only 2 placeholders are supported.
cmd.AppendSwitchNative("missing", L"$3");
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(google_update::kRegDowngradeCleanupCommandField,
cmd.GetCommandLineString().c_str()),
ERROR_SUCCESS);
ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
KEY_SET_VALUE | KEY_WOW64_32KEY)
.WriteValue(
google_update::kRegCleanInstallRequiredForVersionBelowField,
kLastBreakingVersion),
ERROR_SUCCESS);
ASSERT_NO_FATAL_FAILURE(
AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/false));
}
} // namespace installer