chromium/components/crash/content/browser/crash_metrics_reporter_android_unittest.cc

// Copyright 2018 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/crash/content/browser/crash_metrics_reporter_android.h"

#include <signal.h>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace crash_reporter {

class CrashMetricsReporterObserver : public CrashMetricsReporter::Observer {
 public:
  CrashMetricsReporterObserver() {}

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

  ~CrashMetricsReporterObserver() {}

  // CrashMetricsReporter::Observer:
  void OnCrashDumpProcessed(int rph_id,
                            const CrashMetricsReporter::ReportedCrashTypeSet&
                                reported_counts) override {
    recorded_crash_types_ = reported_counts;
    wait_run_loop_.Quit();
  }

  const CrashMetricsReporter::ReportedCrashTypeSet& recorded_crash_types()
      const {
    return recorded_crash_types_;
  }

  void WaitForProcessed() { wait_run_loop_.Run(); }

 private:
  base::RunLoop wait_run_loop_;
  CrashMetricsReporter::ReportedCrashTypeSet recorded_crash_types_;
};

class CrashMetricsReporterTest : public testing::Test {
 public:
  CrashMetricsReporterTest()
      : scoped_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}

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

  ~CrashMetricsReporterTest() override {}

 protected:
  blink::OomInterventionMetrics InterventionMetrics(bool allocation_failed) {
    blink::OomInterventionMetrics metrics;
    metrics.allocation_failed = allocation_failed;
    metrics.current_private_footprint_kb = 100;
    metrics.current_swap_kb = 0;
    metrics.current_vm_size_kb = 0;
    metrics.current_blink_usage_kb = 0;
    return metrics;
  }

  void TestOomCrashProcessing(
      const ChildExitObserver::TerminationInfo& termination_info,
      CrashMetricsReporter::ReportedCrashTypeSet expected_crash_types,
      const char* histogram_name) {
    base::HistogramTester histogram_tester;

    CrashMetricsReporterObserver crash_dump_observer;
    CrashMetricsReporter::GetInstance()->AddObserver(&crash_dump_observer);

    CrashMetricsReporter::GetInstance()->ChildProcessExited(termination_info);
    crash_dump_observer.WaitForProcessed();

    EXPECT_EQ(expected_crash_types, crash_dump_observer.recorded_crash_types());

    if (histogram_name) {
      histogram_tester.ExpectUniqueSample(
          histogram_name, CrashMetricsReporter::EMPTY_MINIDUMP_WHILE_RUNNING,
          1);
    }
    CrashMetricsReporter::GetInstance()->RemoveObserver(&crash_dump_observer);
  }

 private:
  base::test::TaskEnvironment scoped_environment_;
};

TEST_F(CrashMetricsReporterTest, UtilityProcessOOM) {
  ChildExitObserver::TerminationInfo termination_info;
  termination_info.process_host_id = 1;
  termination_info.pid = base::kNullProcessHandle;
  termination_info.process_type = content::PROCESS_TYPE_UTILITY;
  termination_info.app_state =
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES;
  termination_info.normal_termination = false;
  termination_info.binding_state = base::android::ChildBindingState::STRONG;
  termination_info.was_killed_intentionally_by_browser = false;
  termination_info.renderer_has_visible_clients = true;

  TestOomCrashProcessing(
      termination_info,
      {CrashMetricsReporter::ProcessedCrashCounts::kUtilityForegroundOom},
      nullptr);
}

TEST_F(CrashMetricsReporterTest, NormalTerminationIsNotOOMUtilityProcess) {
  ChildExitObserver::TerminationInfo termination_info;
  termination_info.process_host_id = 1;
  termination_info.pid = base::kNullProcessHandle;
  termination_info.process_type = content::PROCESS_TYPE_UTILITY;
  termination_info.app_state =
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES;
  termination_info.normal_termination = true;
  termination_info.binding_state = base::android::ChildBindingState::STRONG;
  termination_info.was_killed_intentionally_by_browser = false;
  termination_info.renderer_has_visible_clients = true;

  TestOomCrashProcessing(termination_info, {}, nullptr);
}

TEST_F(CrashMetricsReporterTest, UtilityProcessAll) {
  ChildExitObserver::TerminationInfo termination_info;
  termination_info.process_host_id = 1;
  termination_info.pid = base::kNullProcessHandle;
  termination_info.process_type = content::PROCESS_TYPE_UTILITY;
  termination_info.app_state =
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES;
  termination_info.crash_signo = SIGSEGV;
  termination_info.normal_termination = false;
  termination_info.binding_state = base::android::ChildBindingState::STRONG;
  termination_info.was_killed_intentionally_by_browser = false;
  termination_info.renderer_has_visible_clients = true;

  CrashMetricsReporterObserver crash_dump_observer;
  CrashMetricsReporter::GetInstance()->AddObserver(&crash_dump_observer);

  CrashMetricsReporter::GetInstance()->ChildProcessExited(termination_info);
  crash_dump_observer.WaitForProcessed();

  EXPECT_EQ(CrashMetricsReporter::ReportedCrashTypeSet(
                {CrashMetricsReporter::ProcessedCrashCounts::kUtilityCrashAll}),
            crash_dump_observer.recorded_crash_types());
  CrashMetricsReporter::GetInstance()->RemoveObserver(&crash_dump_observer);
}

TEST_F(CrashMetricsReporterTest, NormalTerminationIsNotOOM) {
  ChildExitObserver::TerminationInfo termination_info;
  termination_info.process_host_id = 1;
  termination_info.pid = base::kNullProcessHandle;
  termination_info.process_type = content::PROCESS_TYPE_RENDERER;
  termination_info.app_state =
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES;
  termination_info.normal_termination = true;
  termination_info.binding_state = base::android::ChildBindingState::STRONG;
  termination_info.was_killed_intentionally_by_browser = false;
  termination_info.renderer_has_visible_clients = true;
  termination_info.blink_oom_metrics = InterventionMetrics(false);
  TestOomCrashProcessing(termination_info,
                         {CrashMetricsReporter::ProcessedCrashCounts::
                              kRendererForegroundVisibleNormalTermNoMinidump},
                         nullptr);
}

TEST_F(CrashMetricsReporterTest, RendererForegroundCrash) {
  ChildExitObserver::TerminationInfo termination_info;
  termination_info.process_host_id = 1;
  termination_info.pid = base::kNullProcessHandle;
  termination_info.process_type = content::PROCESS_TYPE_RENDERER;
  termination_info.app_state =
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES;
  termination_info.crash_signo = SIGSEGV;
  termination_info.normal_termination = true;
  termination_info.binding_state = base::android::ChildBindingState::STRONG;
  termination_info.was_killed_intentionally_by_browser = true;
  termination_info.renderer_has_visible_clients = true;

  CrashMetricsReporterObserver crash_dump_observer;
  CrashMetricsReporter::GetInstance()->AddObserver(&crash_dump_observer);

  CrashMetricsReporter::GetInstance()->ChildProcessExited(termination_info);
  crash_dump_observer.WaitForProcessed();

  EXPECT_EQ(
      CrashMetricsReporter::ReportedCrashTypeSet(
          {CrashMetricsReporter::ProcessedCrashCounts::
               kRendererForegroundIntentionalKill,
           CrashMetricsReporter::ProcessedCrashCounts::
               kRendererForegroundVisibleCrash,
           CrashMetricsReporter::ProcessedCrashCounts::kRendererCrashAll}),
      crash_dump_observer.recorded_crash_types());
  CrashMetricsReporter::GetInstance()->RemoveObserver(&crash_dump_observer);
}

}  // namespace crash_reporter