#include "chrome/updater/test/unit_test_util.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/function_ref.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/process/process_iterator.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/policy/manager.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/tag.h"
#include "chrome/updater/test/test_scope.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <shlobj.h>
#include "base/win/scoped_handle.h"
#include "chrome/test/base/process_inspector_win.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/test/test_executables.h"
#endif
namespace updater::test {
namespace {
class CustomLogPrinter : public testing::TestEventListener { … };
void SetupFakeUpdaterPrefs(UpdaterScope scope, const base::Version& version) { … }
void SetupFakeUpdaterInstallFolder(UpdaterScope scope,
const base::Version& version,
bool should_create_updater_executable) { … }
void SetupFakeUpdater(UpdaterScope scope,
const base::Version& version,
bool should_create_updater_executable) { … }
}
const char kChromeAppId[] = …;
bool IsProcessRunning(const base::FilePath::StringType& executable_name,
const base::ProcessFilter* filter) { … }
bool WaitForProcessesToExit(const base::FilePath::StringType& executable_name,
base::TimeDelta wait,
const base::ProcessFilter* filter) { … }
bool KillProcesses(const base::FilePath::StringType& executable_name,
int exit_code,
const base::ProcessFilter* filter) { … }
scoped_refptr<PolicyService> CreateTestPolicyService() { … }
std::string GetTestName() { … }
bool DeleteFileAndEmptyParentDirectories(
const std::optional<base::FilePath>& file_path) { … }
base::FilePath GetLogDestinationDir() { … }
void InitLoggingForUnitTest(const base::FilePath& log_base_path) { … }
#if BUILDFLAG(IS_WIN)
namespace {
const wchar_t kProcmonPath[] = L"C:\\tools\\Procmon.exe";
}
void MaybeExcludePathsFromWindowsDefender() {
constexpr char kTestLauncherExcludePathsFromWindowDefender[] =
"exclude-paths-from-win-defender";
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(kTestLauncherExcludePathsFromWindowDefender)) {
return;
}
if (!IsServiceRunning(L"WinDefend")) {
VLOG(1) << "WinDefend is not running, no need to add exclusion paths.";
return;
}
base::FilePath program_files;
base::FilePath program_files_x86;
base::FilePath local_app_data;
if (!base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files) ||
!base::PathService::Get(base::DIR_PROGRAM_FILESX86, &program_files_x86) ||
!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &local_app_data)) {
return;
}
const auto quote_path_value = [](const base::FilePath& path) {
return base::StrCat({L"'", path.value(), L"'"});
};
const std::wstring cmdline =
base::StrCat({L"PowerShell.exe Add-MpPreference -ExclusionPath ",
base::JoinString({quote_path_value(program_files),
quote_path_value(program_files_x86),
quote_path_value(local_app_data)},
L", ")});
base::LaunchOptions options;
options.start_hidden = true;
options.wait = true;
VLOG(1) << "Running: " << cmdline;
base::Process process = base::LaunchProcess(cmdline, options);
LOG_IF(ERROR, !process.IsValid())
<< "Failed to disable Windows Defender: " << cmdline;
}
base::FilePath StartProcmonLogging() {
if (!::IsUserAnAdmin()) {
LOG(WARNING) << __func__
<< ": user is not an admin, skipping procmon logging";
return {};
}
if (!base::PathExists(base::FilePath(kProcmonPath))) {
LOG(WARNING) << __func__
<< ": procmon missing, skipping logging: " << kProcmonPath;
return {};
}
base::FilePath dest_dir = GetLogDestinationDir();
if (dest_dir.empty() || !base::PathExists(dest_dir)) {
LOG(ERROR) << __func__ << ": failed to get log destination dir";
return {};
}
dest_dir = dest_dir.AppendASCII(GetTestName());
if (!base::CreateDirectory(dest_dir)) {
LOG(ERROR) << __func__
<< ": failed to create log destination dir: " << dest_dir;
return {};
}
const base::FilePath pmc_path(GetTestFilePath("ProcmonConfiguration.pmc"));
CHECK(base::PathExists(pmc_path));
const base::FilePath pml_file(
dest_dir.Append(base::ASCIIToWide(base::UnlocalizedTimeFormatWithPattern(
base::Time::Now(), "yyMMdd-HHmmss.'PML'"))));
const std::wstring& cmdline = base::StrCat(
{kProcmonPath, L" /AcceptEula /LoadConfig ",
base::CommandLine::QuoteForCommandLineToArgvW(pmc_path.value()),
L" /BackingFile ",
base::CommandLine::QuoteForCommandLineToArgvW(pml_file.value()),
L" /Quiet /externalcapture"});
base::LaunchOptions options;
options.start_hidden = true;
VLOG(1) << __func__ << ": running: " << cmdline;
const base::Process process = base::LaunchProcess(cmdline, options);
if (!process.IsValid()) {
LOG(ERROR) << __func__ << ": failed to run: " << cmdline;
return {};
}
base::PlatformThread::Sleep(base::Seconds(3));
return pml_file;
}
void StopProcmonLogging(const base::FilePath& pml_file) {
if (!::IsUserAnAdmin() || !base::PathExists(base::FilePath(kProcmonPath)) ||
!pml_file.MatchesFinalExtension(L".PML")) {
return;
}
for (const std::wstring& cmdline :
{base::StrCat({kProcmonPath, L" /Terminate"})}) {
base::LaunchOptions options;
options.start_hidden = true;
options.wait = true;
VLOG(1) << __func__ << ": running: " << cmdline;
const base::Process process = base::LaunchProcess(cmdline, options);
LOG_IF(ERROR, !process.IsValid())
<< __func__ << ": failed to run: " << cmdline;
}
if (!base::CopyFile(pml_file, pml_file.ReplaceExtension(L".PML.BAK"))) {
LOG(ERROR) << __func__ << ": failed to backup pml file";
}
}
EventHolder CreateWaitableEventForTest() {
NamedObjectAttributes attr = GetNamedObjectAttributes(
base::NumberToWString(::GetCurrentProcessId()).c_str(),
GetUpdaterScopeForTesting());
return {base::WaitableEvent(base::win::ScopedHandle(
::CreateEvent(&attr.sa, FALSE, FALSE, attr.name.c_str()))),
attr.name};
}
#endif
const base::ProcessIterator::ProcessEntries FindProcesses(
const base::FilePath::StringType& executable_name,
const base::ProcessFilter* filter) { … }
std::string PrintProcesses(const base::FilePath::StringType& executable_name,
const base::ProcessFilter* filter) { … }
bool WaitFor(base::FunctionRef<bool()> predicate,
base::FunctionRef<void()> still_waiting) { … }
base::FilePath GetTestFilePath(const char* file_name) { … }
void SetupFakeUpdaterVersion(UpdaterScope scope,
const base::Version& base_version,
int major_version_offset,
bool should_create_updater_executable) { … }
void SetupMockUpdater(const base::FilePath& mock_updater_path) { … }
void ExpectOnlyMockUpdater(const base::FilePath& mock_updater_path) { … }
void ExpectTagArgsEqual(const updater::tagging::TagArgs& actual,
const updater::tagging::TagArgs& expected) { … }
int WaitForProcess(base::Process& process) { … }
}