// Copyright 2013 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/test/base/chrome_unit_test_suite.h"

#include <memory>
#include <optional>

#include "base/environment.h"
#include "base/path_service.h"
#include "base/power_monitor/power_monitor.h"
#include "base/process/process_handle.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile_shortcut_manager.h"
#include "chrome/browser/ui/webui/chrome_web_ui_configs.h"
#include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
#include "chrome/browser/update_client/chrome_update_query_params_delegate.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/component_updater/component_updater_paths.h"
#include "components/startup_metric_utils/common/startup_metric_utils.h"
#include "components/update_client/update_query_params.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/webui_config_map.h"
#include "content/public/common/content_paths.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "extensions/buildflags/buildflags.h"
#include "gpu/ipc/service/image_transport_surface.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_handle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/gfx/font_util.h"
#include "ui/gl/test/gl_surface_test_support.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_paths.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chromeos/dbus/constants/dbus_paths.h"
#include "crypto/nss_util_internal.h"
#endif

#if !BUILDFLAG(IS_ANDROID)
#include "base/auto_reset.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/publishers/publisher_host_factory_impl.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_trust_checker.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
#endif  // !BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/common/initialize_extensions_client.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/extension_paths.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

namespace {

constexpr char kDefaultLocale[] = "en-US";

// Creates a TestingBrowserProcess for each test.
class ChromeUnitTestSuiteInitializer : public testing::EmptyTestEventListener {
 public:
  ChromeUnitTestSuiteInitializer() = default;
  ChromeUnitTestSuiteInitializer(const ChromeUnitTestSuiteInitializer&) =
      delete;
  ChromeUnitTestSuiteInitializer& operator=(
      const ChromeUnitTestSuiteInitializer&) = delete;
  ~ChromeUnitTestSuiteInitializer() override = default;

  void OnTestStart(const testing::TestInfo& test_info) override {
    TestingBrowserProcess::CreateInstance();
#if !BUILDFLAG(IS_ANDROID)
    // Injects global publisher factory for unit_tests.
    // TODO(crbug.com/441649482): Consider better testing approach.
    publisher_host_factory_resetter_ =
        apps::AppServiceProxyFactory::GetInstance()->SetPublisherHostFactory(
            std::make_unique<apps::PublisherHostFactoryImpl>());
#endif
    // Make sure the loaded locale is "en-US".
    if (ui::ResourceBundle::GetSharedInstance().GetLoadedLocale() !=
        kDefaultLocale) {
      // Linux uses environment to determine locale.
      std::unique_ptr<base::Environment> env(base::Environment::Create());
      env->SetVar("LANG", kDefaultLocale);
      ui::ResourceBundle::GetSharedInstance().ReloadLocaleResources(
          kDefaultLocale);
    }
  }

  void OnTestEnd(const testing::TestInfo& test_info) override {
    TestingBrowserProcess::TearDownAndDeleteInstance();
    // Some tests cause ChildThreadImpl to initialize a PowerMonitor.
    base::PowerMonitor::GetInstance()->ShutdownForTesting();
#if BUILDFLAG(IS_WIN)
    // Running tests locally on Windows machines with some degree of
    // accessibility enabled can cause this flag to become implicitly set.
    constexpr ui::AXMode kAllowedFlags(ui::AXMode::kNativeAPIs);
#else
    constexpr ui::AXMode kAllowedFlags(ui::AXMode::kNone);
#endif
    if (ui::AXMode disallowed =
            content::BrowserAccessibilityState::GetInstance()
                ->GetAccessibilityMode() &
            ~kAllowedFlags;
        !disallowed.is_mode_off()) {
      CHECK_EQ(disallowed, ui::AXMode())
          << "Use content::ScopedAccessibilityModeOverride or otherwise ensure "
             "that accessibility is disabled at the end of your test.";
    }
#if BUILDFLAG(IS_CHROMEOS)
    arc::ClearArcAllowedCheckForTesting();
    crypto::ResetTokenManagerForTesting();
#endif  // BUILDFLAG(IS_CHROMEOS)
#if !BUILDFLAG(IS_ANDROID)
    web_app::SetTrustedWebBundleIdsForTesting({});
#endif  // !BUILDFLAG(IS_ANDROID)
    browser_shutdown::ResetShutdownGlobalsForTesting();
#if !BUILDFLAG(IS_ANDROID)
    publisher_host_factory_resetter_.reset();
#endif
  }

#if !BUILDFLAG(IS_ANDROID)
  std::optional<base::AutoReset<std::unique_ptr<apps::PublisherHostFactory>>>
      publisher_host_factory_resetter_;
#endif  // !BUILDFLAG(IS_ANDROID)
};

}  // namespace

ChromeUnitTestSuite::ChromeUnitTestSuite(int argc, char** argv)
    : ChromeTestSuite(argc, argv) {}

void ChromeUnitTestSuite::Initialize() {
  // Add an additional listener to do the extra initialization for unit tests.
  // It will be started before the base class listeners and ended after the
  // base class listeners.
  testing::TestEventListeners& listeners =
      testing::UnitTest::GetInstance()->listeners();
  listeners.Append(new ChromeUnitTestSuiteInitializer);
  listeners.Append(new content::CheckForLeakedWebUIRegistrations);

  {
    ChromeContentClient content_client;
    RegisterContentSchemes(&content_client);
  }
  InitializeProviders();
  RegisterInProcessThreads();

  ChromeTestSuite::Initialize();

  gfx::InitializeFonts();

  // This needs to run after ChromeTestSuite::Initialize which calls content's
  // intialization which calls base's which initializes ICU.
  InitializeResourceBundle();

  base::DiscardableMemoryAllocator::SetInstance(&discardable_memory_allocator_);
  ProfileShortcutManager::DisableForUnitTests();

  // BrowserView assumes that application start time is set when it is painted.
  // Since RecordApplicationStartTime() would DCHECK if it was invoked from
  // multiple tests in the same process, invoke it once in test suite
  // initialization.
  startup_metric_utils::GetCommon().RecordApplicationStartTime(
      base::TimeTicks::Now());
}

void ChromeUnitTestSuite::Shutdown() {
  ui::ResourceBundle::CleanupSharedInstance();
  ChromeTestSuite::Shutdown();
}

void ChromeUnitTestSuite::InitializeProviders() {
  chrome::RegisterPathProvider();
  content::RegisterPathProvider();
  ui::RegisterPathProvider();
  component_updater::RegisterPathProvider(chrome::DIR_COMPONENTS,
                                          chrome::DIR_USER_DATA);

#if BUILDFLAG(IS_CHROMEOS)
  ash::RegisterPathProvider();
  CHECK(base::PathService::OverrideWithoutCheckForTesting(
      ash::DIR_USER_DATA,
      base::PathService::CheckedGet(chrome::DIR_USER_DATA)));
  chromeos::dbus_paths::RegisterPathProvider();
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS)
  extensions::RegisterPathProvider();
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  EnsureExtensionsClientInitialized();
#endif

  content::WebUIControllerFactory::RegisterFactory(
      ChromeWebUIControllerFactory::GetInstance());
  RegisterChromeWebUIConfigs();

  gl::GLSurfaceTestSupport::InitializeOneOff();

  update_client::UpdateQueryParams::SetDelegate(
      ChromeUpdateQueryParamsDelegate::GetInstance());
}

void ChromeUnitTestSuite::InitializeResourceBundle() {
  // Force unittests to run using en-US so if we test against string
  // output, it'll pass regardless of the system language.
  ui::ResourceBundle::InitSharedInstanceWithLocale(
      kDefaultLocale, nullptr, ui::ResourceBundle::LOAD_COMMON_RESOURCES);
  base::FilePath resources_pack_path;
  base::PathService::Get(chrome::FILE_RESOURCES_PACK, &resources_pack_path);
  ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
      resources_pack_path, ui::kScaleFactorNone);
}
