chromium/chrome/updater/test/unit_test_util.cc

// Copyright 2020 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/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 {

// CustomLogPrinter intercepts test part results and prints them using
// Chromium logging, so that assertion failures are tagged with process IDs
// and timestamps.
class CustomLogPrinter : public testing::TestEventListener {};

// Creates Prefs with the fake updater version set as active.
void SetupFakeUpdaterPrefs(UpdaterScope scope, const base::Version& version) {}

// Creates an install folder on the system with the fake updater 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) {}

}  // namespace

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";
}  // namespace

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 {};
  }

  // Gives time for the procmon process to start logging. Without a sleep,
  // `procmon` is unable to fully initialize the logging, and subsequently when
  // `procmon /Terminate` is called to terminate the logging `procmon`, it
  // causes the PML log file to corrupt.
  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;
  }

  // Make a copy of the PML file in case the original gets deleted.
  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  // BUILDFLAG(IS_WIN)

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) {}

}  // namespace updater::test