// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/metrics/content/content_stability_metrics_provider.h"

#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "components/metrics/content/extensions_helper.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/hashing.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/common/process_type.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "extensions/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/system_profile.pb.h"

namespace metrics {

namespace {

const char kTestUtilityProcessName[] = "test_utility_process";

class MockExtensionsHelper : public ExtensionsHelper {
 public:
  MockExtensionsHelper() = default;
  MockExtensionsHelper(const MockExtensionsHelper&) = delete;
  MockExtensionsHelper& operator=(const MockExtensionsHelper&) = delete;
  ~MockExtensionsHelper() override = default;

  void set_extension_host(content::RenderProcessHost* host) { host_ = host; }
  // ExtensionsHelper:
  bool IsExtensionProcess(
      content::RenderProcessHost* render_process_host) override {
    return render_process_host == host_;
  }

 private:
  raw_ptr<content::RenderProcessHost> host_ = nullptr;
};

}  // namespace

class ContentStabilityMetricsProviderTest : public testing::Test {
 protected:
  ContentStabilityMetricsProviderTest()
      : prefs_(std::make_unique<TestingPrefServiceSimple>()) {
    metrics::StabilityMetricsHelper::RegisterPrefs(prefs()->registry());
  }
  ContentStabilityMetricsProviderTest(
      const ContentStabilityMetricsProviderTest&) = delete;
  ContentStabilityMetricsProviderTest& operator=(
      const ContentStabilityMetricsProviderTest&) = delete;
  ~ContentStabilityMetricsProviderTest() override = default;

  TestingPrefServiceSimple* prefs() { return prefs_.get(); }

 private:
  std::unique_ptr<TestingPrefServiceSimple> prefs_;
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(ContentStabilityMetricsProviderTest,
       BrowserChildProcessObserverUtility) {
  base::HistogramTester histogram_tester;
  metrics::ContentStabilityMetricsProvider provider(prefs(), nullptr);

  content::ChildProcessData child_process_data(content::PROCESS_TYPE_UTILITY);
  child_process_data.metrics_name = kTestUtilityProcessName;

  provider.BrowserChildProcessLaunchedAndConnected(child_process_data);
  const int kExitCode = 1;
  content::ChildProcessTerminationInfo abnormal_termination_info;
  abnormal_termination_info.status =
      base::TERMINATION_STATUS_ABNORMAL_TERMINATION;
  abnormal_termination_info.exit_code = kExitCode;
  provider.BrowserChildProcessCrashed(child_process_data,
                                      abnormal_termination_info);
  provider.BrowserChildProcessCrashed(child_process_data,
                                      abnormal_termination_info);

  // Verify metrics.
  histogram_tester.ExpectUniqueSample(
      "ChildProcess.Launched.UtilityProcessHash",
      variations::HashName(kTestUtilityProcessName), 1);
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kUtilityLaunch, 1);
  histogram_tester.ExpectUniqueSample(
      "ChildProcess.Crashed.UtilityProcessHash",
      variations::HashName(kTestUtilityProcessName), 2);
  histogram_tester.ExpectUniqueSample(
      "ChildProcess.Crashed.UtilityProcessExitCode", kExitCode, 2);
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kUtilityCrash, 2);
}

#if !BUILDFLAG(IS_ANDROID)
TEST_F(ContentStabilityMetricsProviderTest, RenderProcessObserver) {
  metrics::ContentStabilityMetricsProvider provider(prefs(), nullptr);
  content::TestBrowserContext browser_context;
  content::MockRenderProcessHostFactory rph_factory;
  scoped_refptr<content::SiteInstance> site_instance(
      content::SiteInstance::Create(&browser_context));

  // Owned by rph_factory.
  content::RenderProcessHost* host(rph_factory.CreateRenderProcessHost(
      &browser_context, site_instance.get()));

  base::HistogramTester histogram_tester;

  // Crash and abnormal termination should increment renderer crash count.
  content::ChildProcessTerminationInfo crash_details;
  crash_details.status = base::TERMINATION_STATUS_PROCESS_CRASHED;
  crash_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(host);
  provider.RenderProcessExited(host, crash_details);

  content::ChildProcessTerminationInfo term_details;
  term_details.status = base::TERMINATION_STATUS_ABNORMAL_TERMINATION;
  term_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(host);
  provider.RenderProcessExited(host, term_details);

  // Kill does not increment renderer crash count.
  content::ChildProcessTerminationInfo kill_details;
  kill_details.status = base::TERMINATION_STATUS_PROCESS_WAS_KILLED;
  kill_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(host);
  provider.RenderProcessExited(host, kill_details);

  // Failed launch increments failed launch count.
  content::ChildProcessTerminationInfo failed_launch_details;
  failed_launch_details.status = base::TERMINATION_STATUS_LAUNCH_FAILED;
  failed_launch_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(host);
  provider.RenderProcessExited(host, failed_launch_details);

  // Verify metrics.
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kRendererCrash, 2);
  histogram_tester.ExpectBucketCount(
      "Stability.Counts2", StabilityEventType::kRendererFailedLaunch, 1);
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kExtensionCrash, 0);
}

TEST_F(ContentStabilityMetricsProviderTest,
       MetricsServicesWebContentsObserver) {
  metrics::ContentStabilityMetricsProvider provider(prefs(), nullptr);
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kPageLoad, 0);

  // Simulate page loads.
  const auto expected_page_load_count = 4;
  for (int i = 0; i < expected_page_load_count; i++) {
    provider.OnPageLoadStarted();
  }

  // Verify metrics.
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kPageLoad,
                                     expected_page_load_count);
}

#endif  // !BUILDFLAG(IS_ANDROID)

// Assertions for an extension related crash.
// This test only works if extensions are enabled as there is a DCHECK in
// StabilityMetricsHelper that it is only called with a value of true for
// extension process if extensions are enabled.
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(ContentStabilityMetricsProviderTest, ExtensionsNotificationObserver) {
  content::TestBrowserContext browser_context;
  content::MockRenderProcessHostFactory rph_factory;
  scoped_refptr<content::SiteInstance> site_instance(
      content::SiteInstance::Create(&browser_context));

  // Owned by rph_factory.
  content::RenderProcessHost* extension_host =
      rph_factory.CreateRenderProcessHost(&browser_context,
                                          site_instance.get());
  auto extensions_helper = std::make_unique<MockExtensionsHelper>();
  extensions_helper->set_extension_host(extension_host);
  metrics::ContentStabilityMetricsProvider provider(
      prefs(), std::move(extensions_helper));

  base::HistogramTester histogram_tester;

  // Crash and abnormal termination should increment extension crash count.
  content::ChildProcessTerminationInfo crash_details;
  crash_details.status = base::TERMINATION_STATUS_PROCESS_CRASHED;
  crash_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(extension_host);
  provider.RenderProcessExited(extension_host, crash_details);

  // Failed launch increments failed launch count.
  content::ChildProcessTerminationInfo failed_launch_details;
  failed_launch_details.status = base::TERMINATION_STATUS_LAUNCH_FAILED;
  failed_launch_details.exit_code = 1;
  provider.OnRenderProcessHostCreated(extension_host);
  provider.RenderProcessExited(extension_host, failed_launch_details);

  // Verify metrics.
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kRendererCrash, 0);
  histogram_tester.ExpectBucketCount("Stability.Counts2",
                                     StabilityEventType::kExtensionCrash, 1);
  histogram_tester.ExpectBucketCount(
      "Stability.Counts2", StabilityEventType::kExtensionRendererFailedLaunch,
      1);
}
#endif

}  // namespace metrics
