blob: 3b226401d825cc59d7c206701f80f98598b5b0b1 [file] [log] [blame]
// Copyright 2018 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.
// This file tests launching notification_helper.exe by the OS via the registry.
// An advanced version of this test is in
// chrome/browser/notifications/notification_helper_launches_chrome_unittest.cc,
// which additionally tests if chrome.exe can be successfully launched by
// notification_helper.exe via the NotificationActivator::Activate function.
#include <wrl/client.h>
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/process/process_iterator.h"
#include "base/strings/string16.h"
#include "base/test/test_timeouts.h"
#include "base/win/scoped_winrt_initializer.h"
#include "base/win/windows_types.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/registry_key_backup.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item.h"
#include "chrome/installer/util/work_item_list.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Returns the process with name |name| if it is found.
base::Process FindProcess(const base::string16& name) {
unsigned int pid;
{
base::NamedProcessIterator iter(name, nullptr);
const auto* entry = iter.NextProcessEntry();
if (!entry)
return base::Process();
pid = entry->pid();
}
auto process = base::Process::Open(pid);
if (!process.IsValid())
return process;
// Since the process could go away suddenly before we open a handle to it,
// it's possible that a different process was just opened and assigned the
// same PID due to aggressive PID reuse. Now that a handle is held to *some*
// process, take another run through the snapshot to see if the process with
// this PID has the right exe name.
base::NamedProcessIterator iter(name, nullptr);
while (const auto* entry = iter.NextProcessEntry()) {
if (entry->pid() == pid)
return process; // PID was not reused since the PID's match.
}
return base::Process(); // The PID was reused.
}
} // namespace
class NotificationHelperTest : public testing::Test {
protected:
NotificationHelperTest()
: toast_activator_reg_path_(InstallUtil::GetToastActivatorRegistryPath()),
root_(HKEY_CURRENT_USER) {}
void SetUp() override {
// Back up the existing registration.
ASSERT_TRUE(backup_.Initialize(root_, toast_activator_reg_path_.c_str(),
WorkItem::kWow64Default));
ASSERT_NO_FATAL_FAILURE(RegisterServer());
}
void TearDown() override {
ASSERT_NO_FATAL_FAILURE(UnregisterServer());
// Restore the registration.
ASSERT_TRUE(backup_.WriteTo(root_, toast_activator_reg_path_.c_str(),
WorkItem::kWow64Default));
}
private:
// Registers notification_helper.exe as the server.
void RegisterServer() {
std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
// Delete the old registration to ensure a clean environment for server
// registration. This is okay because we have already backed up the existing
// registration in SetUp(), and will restore it in TearDown().
list->AddDeleteRegKeyWorkItem(root_, toast_activator_reg_path_,
WorkItem::kWow64Default);
// Notification_helper.exe is in the build output directory next to this
// test executable, as the test build target has a data_deps dependency on
// it.
base::FilePath dir_exe;
ASSERT_TRUE(PathService::Get(base::DIR_EXE, &dir_exe));
base::FilePath notification_helper =
dir_exe.Append(installer::kNotificationHelperExe);
base::string16 toast_activator_server_path = toast_activator_reg_path_;
toast_activator_server_path.append(L"\\LocalServer32");
// Command-line featuring the quoted path to the exe.
base::string16 command(1, L'"');
command.append(notification_helper.value()).append(1, L'"');
list->AddCreateRegKeyWorkItem(root_, toast_activator_server_path,
WorkItem::kWow64Default);
list->AddSetRegValueWorkItem(root_, toast_activator_server_path,
WorkItem::kWow64Default, L"", command, true);
list->AddSetRegValueWorkItem(root_, toast_activator_server_path,
WorkItem::kWow64Default, L"ServerExecutable",
notification_helper.value(), true);
ASSERT_TRUE(list->Do());
}
// Unregisters the server by deleting the registry key installed during the
// test.
void UnregisterServer() {
ASSERT_TRUE(InstallUtil::DeleteRegistryKey(root_, toast_activator_reg_path_,
WorkItem::kWow64Default));
}
// Path to the toast activator registry.
const base::string16 toast_activator_reg_path_;
// Predefined handle to the registry.
const HKEY root_;
// Backup of the deleted registry.
RegistryKeyBackup backup_;
DISALLOW_COPY_AND_ASSIGN(NotificationHelperTest);
};
TEST_F(NotificationHelperTest, NotificationHelperServerTest) {
base::win::ScopedWinrtInitializer winrt_initializer;
ASSERT_TRUE(winrt_initializer.Succeeded());
// There isn't a way to directly correlate the notification_helper.exe server
// to this test. So we need to hunt for the server.
// Make sure there is no notification_helper process running around.
base::Process helper = FindProcess(installer::kNotificationHelperExe);
ASSERT_FALSE(helper.IsValid());
Microsoft::WRL::ComPtr<IUnknown> notification_activator;
ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance(
install_static::GetToastActivatorClsid(), nullptr, CLSCTX_LOCAL_SERVER,
IID_PPV_ARGS(&notification_activator)));
ASSERT_TRUE(notification_activator);
// The server is now invoked upon the request of creating the object instance.
// The server module now holds a reference of the instance object, the
// notification_helper.exe process is alive waiting for that reference to be
// released.
helper = FindProcess(installer::kNotificationHelperExe);
ASSERT_TRUE(helper.IsValid());
// Release the instance object. Now that the last (and the only) instance
// object of the module is released, the event living in the server
// process is signaled, which allows the server process to exit.
notification_activator.Reset();
ASSERT_TRUE(
helper.WaitForExitWithTimeout(TestTimeouts::action_timeout(), nullptr));
}