blob: 0194376e3922926c1e0df633a2f48f6ddfcfdb66 [file] [log] [blame]
// 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/conflicts/installed_programs_win.h"
#include <map>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "chrome/browser/conflicts/msi_util_win.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
static const wchar_t kRegistryKeyPathFormat[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%ls";
struct CommonInfo {
base::string16 product_id;
bool is_system_component;
bool is_microsoft_published;
base::string16 display_name;
base::string16 uninstall_string;
};
struct InstallLocationProgramInfo {
CommonInfo common_info;
base::string16 install_location;
};
struct MsiProgramInfo {
CommonInfo common_info;
std::vector<base::string16> components;
};
class MockMsiUtil : public MsiUtil {
public:
MockMsiUtil(const std::map<base::string16, std::vector<base::string16>>&
component_paths_map)
: component_paths_map_(component_paths_map) {}
bool GetMsiComponentPaths(
const base::string16& product_guid,
std::vector<base::string16>* component_paths) const override {
auto iter = component_paths_map_.find(product_guid);
if (iter == component_paths_map_.end())
return false;
*component_paths = iter->second;
return true;
}
private:
const std::map<base::string16, std::vector<base::string16>>&
component_paths_map_;
};
class TestInstalledPrograms : public InstalledPrograms {
public:
using InstalledPrograms::Initialize;
};
class InstalledProgramsTest : public testing::Test {
public:
InstalledProgramsTest() = default;
~InstalledProgramsTest() override = default;
// ASSERT_NO_FATAL_FAILURE cannot be used in a constructor so the registry
// hive overrides are done here.
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
}
void AddCommonInfo(const CommonInfo& common_info,
base::win::RegKey* registry_key) {
registry_key->WriteValue(L"SystemComponent",
common_info.is_system_component ? 1 : 0);
registry_key->WriteValue(L"UninstallString",
common_info.uninstall_string.c_str());
if (common_info.is_microsoft_published)
registry_key->WriteValue(L"Publisher", L"Microsoft Corporation");
registry_key->WriteValue(L"DisplayName", common_info.display_name.c_str());
}
void AddFakeProgram(const MsiProgramInfo& program_info) {
const base::string16 registry_key_path = base::StringPrintf(
kRegistryKeyPathFormat, program_info.common_info.product_id.c_str());
base::win::RegKey registry_key(HKEY_CURRENT_USER, registry_key_path.c_str(),
KEY_WRITE);
AddCommonInfo(program_info.common_info, &registry_key);
component_paths_map_.insert(
{program_info.common_info.product_id, program_info.components});
}
void AddFakeProgram(const InstallLocationProgramInfo& program_info) {
const base::string16 registry_key_path = base::StringPrintf(
kRegistryKeyPathFormat, program_info.common_info.product_id.c_str());
base::win::RegKey registry_key(HKEY_CURRENT_USER, registry_key_path.c_str(),
KEY_WRITE);
AddCommonInfo(program_info.common_info, &registry_key);
registry_key.WriteValue(L"InstallLocation",
program_info.install_location.c_str());
}
TestInstalledPrograms& installed_programs() { return installed_programs_; }
void InitializeInstalledProgramsSynchronously() {
installed_programs_.Initialize(
base::Closure(), base::MakeUnique<MockMsiUtil>(component_paths_map_));
scoped_task_environment_.RunUntilIdle();
}
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
registry_util::RegistryOverrideManager registry_override_manager_;
TestInstalledPrograms installed_programs_;
// This should not get modified after InitializeInstalledPrograms() is called
// because it will end up being accessed on a different thread by MockMsiUtil.
std::map<base::string16, std::vector<base::string16>> component_paths_map_;
DISALLOW_COPY_AND_ASSIGN(InstalledProgramsTest);
};
} // namespace
// Checks that registry entries with invalid information are skipped.
TEST_F(InstalledProgramsTest, InvalidEntries) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocation[] = L"c:\\program files\\program\\";
InstallLocationProgramInfo kTestCases[] = {
{
{
L"Is SystemComponent", true, false, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Is Microsoft published", false, true, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Missing DisplayName", false, false, L"", kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Missing UninstallString", false, false, kValidDisplayName, L"",
},
kInstallLocation,
},
};
for (const auto& test_case : kTestCases)
AddFakeProgram(test_case);
InitializeInstalledProgramsSynchronously();
// None of the invalid entries were picked up.
const base::FilePath valid_child_file =
base::FilePath(kInstallLocation).Append(L"file.dll");
std::vector<base::string16> program_names;
EXPECT_FALSE(installed_programs().GetInstalledProgramNames(valid_child_file,
&program_names));
}
// Tests InstalledPrograms on a valid entry with an InstallLocation.
TEST_F(InstalledProgramsTest, InstallLocation) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocation[] = L"c:\\program files\\program\\";
InstallLocationProgramInfo kTestCase = {
{
L"Completely valid", false, false, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
};
AddFakeProgram(kTestCase);
InitializeInstalledProgramsSynchronously();
// Child file path.
const base::FilePath valid_child_file =
base::FilePath(kInstallLocation).Append(L"file.dll");
std::vector<base::string16> program_names;
EXPECT_TRUE(installed_programs().GetInstalledProgramNames(valid_child_file,
&program_names));
ASSERT_EQ(1u, program_names.size());
EXPECT_EQ(kTestCase.common_info.display_name, program_names[0]);
// Non-child file path.
const base::FilePath invalid_child_file(
L"c:\\program files\\another program\\test.dll");
EXPECT_FALSE(installed_programs().GetInstalledProgramNames(invalid_child_file,
&program_names));
}
// Tests InstalledPrograms on a valid MSI entry.
TEST_F(InstalledProgramsTest, Msi) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
MsiProgramInfo kTestCase = {
{
L"Completely valid", false, false, kValidDisplayName,
kValidUninstallString,
},
{
L"c:\\program files\\program\\file1.dll",
L"c:\\program files\\program\\file2.dll",
L"c:\\program files\\program\\sub\\file3.dll",
L"c:\\windows\\system32\\file4.dll",
},
};
AddFakeProgram(kTestCase);
InitializeInstalledProgramsSynchronously();
// Checks that all the files match the program.
for (const auto& component : kTestCase.components) {
std::vector<base::string16> program_names;
EXPECT_TRUE(installed_programs().GetInstalledProgramNames(
base::FilePath(component), &program_names));
ASSERT_EQ(1u, program_names.size());
EXPECT_EQ(kTestCase.common_info.display_name, program_names[0]);
}
// Any other file shouldn't work.
const base::FilePath invalid_child_file(
L"c:\\program files\\another program\\test.dll");
std::vector<base::string16> program_names;
EXPECT_FALSE(installed_programs().GetInstalledProgramNames(invalid_child_file,
&program_names));
}
// Checks that if a file matches an InstallLocation and an MSI component, only
// the MSI program will be considered.
TEST_F(InstalledProgramsTest, PrioritizeMsi) {
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocationDisplayName[] = L"InstallLocation DisplayName";
const wchar_t kMsiDisplayName[] = L"Msi DisplayName";
const wchar_t kInstallLocation[] = L"c:\\program files\\program\\";
const wchar_t kMsiComponent[] = L"c:\\program files\\program\\file.dll";
InstallLocationProgramInfo kInstallLocationFakeProgram = {
{
L"GUID1", false, false, kInstallLocationDisplayName,
kValidUninstallString,
},
kInstallLocation,
};
MsiProgramInfo kMsiFakeProgram = {
{
L"GUID2", false, false, kMsiDisplayName, kValidUninstallString,
},
{
kMsiComponent,
},
};
AddFakeProgram(kInstallLocationFakeProgram);
AddFakeProgram(kMsiFakeProgram);
InitializeInstalledProgramsSynchronously();
std::vector<base::string16> program_names;
EXPECT_TRUE(installed_programs().GetInstalledProgramNames(
base::FilePath(kMsiComponent), &program_names));
ASSERT_EQ(1u, program_names.size());
EXPECT_NE(kInstallLocationDisplayName, program_names[0]);
EXPECT_EQ(kMsiDisplayName, program_names[0]);
}
// Tests that if 2 entries with an InstallLocation exist, both are ignored.
TEST_F(InstalledProgramsTest, ConflictingInstallLocations) {
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kDisplayName1[] = L"DisplayName1";
const wchar_t kDisplayName2[] = L"DisplayName2";
const wchar_t kInstallLocationParent[] = L"c:\\program files\\company\\";
const wchar_t kInstallLocationChild[] =
L"c:\\program files\\company\\program";
const wchar_t kFile[] = L"c:\\program files\\company\\program\\file.dll";
InstallLocationProgramInfo kFakeProgram1 = {
{
L"GUID1", false, false, kDisplayName1, kValidUninstallString,
},
kInstallLocationParent,
};
InstallLocationProgramInfo kFakeProgram2 = {
{
L"GUID2", false, false, kDisplayName2, kValidUninstallString,
},
kInstallLocationChild,
};
AddFakeProgram(kFakeProgram1);
AddFakeProgram(kFakeProgram2);
InitializeInstalledProgramsSynchronously();
std::vector<base::string16> program_names;
EXPECT_FALSE(installed_programs().GetInstalledProgramNames(
base::FilePath(kFile), &program_names));
}