chromium/chrome/browser/win/conflicts/module_database_unittest.cc

// Copyright 2017 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/browser/win/conflicts/module_database.h"

#include <memory>
#include <optional>

#include "base/functional/bind.h"
#include "base/time/time.h"
#include "chrome/browser/win/conflicts/module_database_observer.h"
#include "chrome/browser/win/conflicts/module_info.h"
#include "chrome/services/util_win/util_win_impl.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr content::ProcessType kProcessType1 = content::PROCESS_TYPE_BROWSER;
constexpr content::ProcessType kProcessType2 = content::PROCESS_TYPE_RENDERER;

constexpr wchar_t kDll1[] = L"dummy.dll";
constexpr wchar_t kDll2[] = L"foo.dll";

constexpr size_t kSize1 = 100 * 4096;
constexpr size_t kSize2 = 20 * 4096;

constexpr uint32_t kTime1 = 0xDEADBEEF;
constexpr uint32_t kTime2 = 0xBAADF00D;

}  // namespace

class ModuleDatabaseTest : public testing::Test {
 public:
  ModuleDatabaseTest(const ModuleDatabaseTest&) = delete;
  ModuleDatabaseTest& operator=(const ModuleDatabaseTest&) = delete;

 protected:
  ModuleDatabaseTest()
      : dll1_(kDll1),
        dll2_(kDll2),
        task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
        module_database_(std::make_unique<ModuleDatabase>(
            /* third_party_blocking_policy_enabled = */ false)) {
    module_database_->module_inspector_.SetUtilWinFactoryCallbackForTesting(
        base::BindRepeating(&ModuleDatabaseTest::CreateUtilWinService,
                            base::Unretained(this)));
    module_database_->StartInspection();
  }

  ~ModuleDatabaseTest() override {
    module_database_ = nullptr;

    // Clear the outstanding delayed tasks that were posted by the
    // ModuleDatabase instance.
    task_environment_.FastForwardUntilNoTasksRemain();
  }

  const ModuleDatabase::ModuleMap& modules() {
    return module_database_->modules_;
  }

  ModuleDatabase* module_database() { return module_database_.get(); }

  void RunSchedulerUntilIdle() { task_environment_.RunUntilIdle(); }

  void FastForwardToIdleTimer() {
    task_environment_.FastForwardBy(ModuleDatabase::kIdleTimeout);
    task_environment_.RunUntilIdle();
  }

  const base::FilePath dll1_;
  const base::FilePath dll2_;

 private:
  mojo::Remote<chrome::mojom::UtilWin> CreateUtilWinService() {
    mojo::Remote<chrome::mojom::UtilWin> remote;
    util_win_impl_.emplace(remote.BindNewPipeAndPassReceiver());
    return remote;
  }

  // Must be before |module_database_|.
  content::BrowserTaskEnvironment task_environment_;

  ScopedTestingLocalState scoped_testing_local_state_;

  std::optional<UtilWinImpl> util_win_impl_;

  std::unique_ptr<ModuleDatabase> module_database_;
};

TEST_F(ModuleDatabaseTest, DatabaseIsConsistent) {
  EXPECT_EQ(0u, modules().size());

  // Load a module.
  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  EXPECT_EQ(1u, modules().size());

  // Ensure that the process and module sets are up to date.
  auto m1 = modules().begin();
  EXPECT_EQ(dll1_, m1->first.module_path);
  EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
            m1->second.process_types);

  // Provide a redundant load message for that module.
  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  EXPECT_EQ(1u, modules().size());

  // Ensure that the process and module sets haven't changed.
  EXPECT_EQ(dll1_, m1->first.module_path);
  EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
            m1->second.process_types);

  // Load a second module into the process.
  module_database()->OnModuleLoad(kProcessType1, dll2_, kSize2, kTime2);
  EXPECT_EQ(2u, modules().size());

  // Ensure that the process and module sets are up to date.
  auto m2 = modules().rbegin();
  EXPECT_EQ(dll2_, m2->first.module_path);
  EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
            m2->second.process_types);

  // Load the dummy.dll in the second process as well.
  module_database()->OnModuleLoad(kProcessType2, dll1_, kSize1, kTime1);
  EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER) |
                ProcessTypeToBit(content::PROCESS_TYPE_RENDERER),
            m1->second.process_types);
}

// A dummy observer that only counts how many notifications it receives.
class DummyObserver : public ModuleDatabaseObserver {
 public:
  DummyObserver() = default;

  DummyObserver(const DummyObserver&) = delete;
  DummyObserver& operator=(const DummyObserver&) = delete;

  ~DummyObserver() override = default;

  void OnNewModuleFound(const ModuleInfoKey& module_key,
                        const ModuleInfoData& module_data) override {
    new_module_count_++;
  }

  void OnKnownModuleLoaded(const ModuleInfoKey& module_key,
                           const ModuleInfoData& module_data) override {
    known_module_loaded_count_++;
  }

  void OnModuleDatabaseIdle() override {
    on_module_database_idle_called_ = true;
  }

  int new_module_count() { return new_module_count_; }
  int known_module_loaded_count() { return known_module_loaded_count_; }
  bool on_module_database_idle_called() {
    return on_module_database_idle_called_;
  }

 private:
  int new_module_count_ = 0;
  int known_module_loaded_count_ = 0;
  bool on_module_database_idle_called_ = false;
};

TEST_F(ModuleDatabaseTest, Observers) {
  // Assume there is no shell extensions or IMEs.
  module_database()->OnShellExtensionEnumerationFinished();
  module_database()->OnImeEnumerationFinished();

  DummyObserver before_load_observer;
  EXPECT_EQ(0, before_load_observer.new_module_count());

  module_database()->AddObserver(&before_load_observer);
  EXPECT_EQ(0, before_load_observer.new_module_count());

  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  RunSchedulerUntilIdle();

  EXPECT_EQ(1, before_load_observer.new_module_count());
  module_database()->RemoveObserver(&before_load_observer);

  // New observers get notified for past loaded modules.
  DummyObserver after_load_observer;
  EXPECT_EQ(0, after_load_observer.new_module_count());

  module_database()->AddObserver(&after_load_observer);
  EXPECT_EQ(1, after_load_observer.new_module_count());

  module_database()->RemoveObserver(&after_load_observer);
}

TEST_F(ModuleDatabaseTest, OnKnownModuleLoaded) {
  DummyObserver dummy_observer;
  module_database()->AddObserver(&dummy_observer);

  EXPECT_EQ(0, dummy_observer.new_module_count());
  EXPECT_EQ(0, dummy_observer.known_module_loaded_count());

  // Assume there is one shell extension.
  module_database()->OnShellExtensionEnumerated(dll1_, kSize1, kTime1);
  module_database()->OnShellExtensionEnumerationFinished();
  module_database()->OnImeEnumerationFinished();

  RunSchedulerUntilIdle();

  EXPECT_EQ(1, dummy_observer.new_module_count());
  EXPECT_EQ(0, dummy_observer.known_module_loaded_count());

  // Pretend the shell extension loads.
  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  RunSchedulerUntilIdle();

  EXPECT_EQ(1, dummy_observer.new_module_count());
  EXPECT_EQ(1, dummy_observer.known_module_loaded_count());

  module_database()->RemoveObserver(&dummy_observer);
}

// Tests the idle cycle of the ModuleDatabase.
TEST_F(ModuleDatabaseTest, IsIdle) {
  // Assume there is no shell extensions or IMEs.
  module_database()->OnShellExtensionEnumerationFinished();
  module_database()->OnImeEnumerationFinished();

  // ModuleDatabase starts busy.
  EXPECT_FALSE(module_database()->IsIdle());

  // Can't fast forward to idle because a module load event is needed.
  FastForwardToIdleTimer();
  EXPECT_FALSE(module_database()->IsIdle());

  // A load module event starts the timer.
  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  EXPECT_FALSE(module_database()->IsIdle());

  FastForwardToIdleTimer();
  EXPECT_TRUE(module_database()->IsIdle());

  // A new shell extension resets the timer.
  module_database()->OnShellExtensionEnumerated(dll1_, kSize1, kTime1);
  EXPECT_FALSE(module_database()->IsIdle());

  FastForwardToIdleTimer();
  EXPECT_TRUE(module_database()->IsIdle());

  // Adding an observer while idle immediately calls OnModuleDatabaseIdle().
  DummyObserver is_idle_observer;
  module_database()->AddObserver(&is_idle_observer);
  EXPECT_TRUE(is_idle_observer.on_module_database_idle_called());

  module_database()->RemoveObserver(&is_idle_observer);

  // Make the ModuleDabatase busy.
  module_database()->OnModuleLoad(kProcessType2, dll2_, kSize2, kTime2);
  EXPECT_FALSE(module_database()->IsIdle());

  // Adding an observer while busy doesn't.
  DummyObserver is_busy_observer;
  module_database()->AddObserver(&is_busy_observer);
  EXPECT_FALSE(is_busy_observer.on_module_database_idle_called());

  // Fast forward will call OnModuleDatabaseIdle().
  FastForwardToIdleTimer();
  EXPECT_TRUE(module_database()->IsIdle());
  EXPECT_TRUE(is_busy_observer.on_module_database_idle_called());

  module_database()->RemoveObserver(&is_busy_observer);
}

// The ModuleDatabase waits until shell extensions and IMEs are enumerated
// before notifying observers or going idle.
TEST_F(ModuleDatabaseTest, WaitUntilRegisteredModulesEnumerated) {
  // This observer is added before the first loaded module.
  DummyObserver before_load_observer;
  module_database()->AddObserver(&before_load_observer);
  EXPECT_EQ(0, before_load_observer.new_module_count());

  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
  FastForwardToIdleTimer();

  // Idle state is prevented.
  EXPECT_FALSE(module_database()->IsIdle());
  EXPECT_EQ(0, before_load_observer.new_module_count());
  EXPECT_FALSE(before_load_observer.on_module_database_idle_called());

  // This observer is added after the first loaded module.
  DummyObserver after_load_observer;
  module_database()->AddObserver(&after_load_observer);
  EXPECT_EQ(0, after_load_observer.new_module_count());
  EXPECT_FALSE(after_load_observer.on_module_database_idle_called());

  // Simulate the enumerations ending.
  module_database()->OnImeEnumerationFinished();
  module_database()->OnShellExtensionEnumerationFinished();

  EXPECT_EQ(1, before_load_observer.new_module_count());
  EXPECT_TRUE(before_load_observer.on_module_database_idle_called());
  EXPECT_EQ(1, after_load_observer.new_module_count());
  EXPECT_TRUE(after_load_observer.on_module_database_idle_called());

  module_database()->RemoveObserver(&after_load_observer);
  module_database()->RemoveObserver(&before_load_observer);
}