// 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 "base/threading/hang_watcher.h"

#include <memory>
#include <optional>

#include "base/barrier_closure.h"
#include "base/containers/fixed_flat_map.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/manual_hang_watcher.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/power_monitor_test.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/threading_features.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace {

using ::base::test::FeatureRefAndParams;
using ::base::test::ManualHangWatcher;
using ::base::test::ScopedFeatureList;
using ::base::test::SingleThreadTaskEnvironment;
using ::base::test::TaskEnvironment;
using ::testing::ElementsAre;
using ::testing::IsEmpty;

#if BUILDFLAG(ARKWEB_CRASHPAD)
void ReportRenderFreeze(int32_t pid, const std::string& processName, const std::string& freezeMsg, int32_t uid) {}
#endif
using ::testing::Pair;
using ::testing::TestWithParam;
using ::testing::UnorderedElementsAre;
using ::testing::Values;
using ::testing::ValuesIn;

// Use this value to mark things very far off in the future. Adding this
// to TimeTicks::Now() gives a point that will never be reached during the
// normal execution of a test.
constexpr TimeDelta kVeryLongDelta{base::Days(365)};

// A relatively small time delta to ensure ordering of hung threads list.
constexpr TimeDelta kSmallCPUQuantum{base::Milliseconds(1)};

constexpr uint64_t kArbitraryDeadline = 0x0000C0FFEEC0FFEEu;
constexpr uint64_t kAllOnes = 0xFFFFFFFFFFFFFFFFu;
constexpr uint64_t kAllZeros = 0x0000000000000000u;
constexpr uint64_t kOnesThenZeroes = 0xAAAAAAAAAAAAAAAAu;
constexpr uint64_t kZeroesThenOnes = 0x5555555555555555u;

// Waits on provided WaitableEvent before executing and signals when done.
class BlockedThread : public DelegateSimpleThread::Delegate {
 public:
  BlockedThread(HangWatcher::ThreadType thread_type, TimeDelta timeout)
      : thread_(this, "BlockedThread"),
        thread_type_(thread_type),
        timeout_(timeout) {
    StartAndWaitForScopeEntered();
  }

  ~BlockedThread() override {
    Unblock();
    thread_.Join();
  }

  void Run() override {
    // Open a scope so that `unregister_closure` is destroyed before signaling
    // `run_event_`.
    {
      // (Un)Register the thread here instead of in ctor/dtor so that the action
      // happens on the right thread.
      base::ScopedClosureRunner unregister_closure =
          base::HangWatcher::RegisterThread(thread_type_);

      WatchHangsInScope scope(timeout_);
      wait_until_entered_scope_.Signal();

      unblock_thread_.Wait();
    }
    run_event_.Signal();
  }

  bool IsDone() { return run_event_.IsSignaled(); }

  void StartAndWaitForScopeEntered() {
    thread_.Start();
    // Block until this thread registered itself for hang watching and has
    // entered a WatchHangsInScope.
    wait_until_entered_scope_.Wait();
  }

  void Unblock() { unblock_thread_.Signal(); }

  void WaitDone() { run_event_.Wait(); }

  PlatformThreadId GetId() { return thread_.tid(); }

 private:
  base::DelegateSimpleThread thread_;

  // Will be signaled once the thread is properly registered for watching and
  // the WatchHangsInScope has been entered.
  WaitableEvent wait_until_entered_scope_;

  // Will be signaled once ThreadMain has run.
  WaitableEvent run_event_;

  // Used to unblock the monitored thread. Signaled from the test main thread.
  base::WaitableEvent unblock_thread_;

  const HangWatcher::ThreadType thread_type_;
  const base::TimeDelta timeout_;
};

// Scope object starting a BlockedThread for all thread types monitored by the
// HangWatcher. Threads are started by the constructor and joined in the
// destructor.
class BlockedThreadsForAllTypes {
 public:
  explicit BlockedThreadsForAllTypes(base::TimeDelta timeout)
      : main_(HangWatcher::ThreadType::kMainThread, timeout),
        compositor_(HangWatcher::ThreadType::kCompositorThread, timeout),
        io_(HangWatcher::ThreadType::kIOThread, timeout),
        pool_(HangWatcher::ThreadType::kThreadPoolThread, timeout) {}

 private:
  BlockedThread main_;
  BlockedThread compositor_;
  BlockedThread io_;
  BlockedThread pool_;
};

class HangWatcherTest : public testing::Test {
 protected:
  // Used exclusively for MOCK_TIME. No tasks will be run on the environment.
  // Single threaded to avoid ThreadPool WorkerThreads registering.
  test::SingleThreadTaskEnvironment task_environment_{
      test::TaskEnvironment::TimeSource::MOCK_TIME};
};

using HangWatcherEnabledTest = TestWithParam<HangWatcher::ProcessType>;
INSTANTIATE_TEST_SUITE_P(AllEnabledProcessTypes,
                         HangWatcherEnabledTest,
                         Values(HangWatcher::ProcessType::kBrowserProcess,
                                HangWatcher::ProcessType::kRendererProcess,
                                HangWatcher::ProcessType::kUtilityProcess));
TEST_P(HangWatcherEnabledTest, HangWatcherEnabled) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(GetParam());
  EXPECT_TRUE(hang_watcher.IsEnabled());
}

TEST(HangWatcherGpuEnabledTest, HangWatcherDisabledOnGpuProcessByDefault) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);
  EXPECT_FALSE(hang_watcher.IsEnabled());
}

TEST(HangWatcherGpuEnabledTest, HangWatcherEnabledOnGpuProcessViaFeature) {
  ScopedFeatureList enable_gpu_watcher(kEnableHangWatcherOnGpuProcess);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);
  EXPECT_TRUE(hang_watcher.IsEnabled());
}

TEST_F(HangWatcherTest, InvalidatingExpectationsPreventsCapture) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // Create a hang.
  WatchHangsInScope expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));

  // de-activate hang watching,
  base::HangWatcher::InvalidateActiveExpectations();

  // Trigger a monitoring on HangWatcher thread and verify results.
  // Hang is not detected.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}

TEST_F(HangWatcherTest, MultipleInvalidateExpectationsDoNotCancelOut) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // Create a hang.
  WatchHangsInScope expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));

  // de-activate hang watching,
  base::HangWatcher::InvalidateActiveExpectations();

  // Redundantly de-activate hang watching.
  base::HangWatcher::InvalidateActiveExpectations();

  // Trigger a monitoring on HangWatcher thread and verify results.
  // Hang is not detected.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}

// TODO(crbug.com/385732561): Test is flaky.
TEST_F(HangWatcherTest,
       DISABLED_NewInnerWatchHangsInScopeAfterInvalidationDetectsHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  WatchHangsInScope expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));

  // De-activate hang watching.
  base::HangWatcher::InvalidateActiveExpectations();

  {
    WatchHangsInScope also_expires_instantly(base::TimeDelta{});
    task_environment_.FastForwardBy(base::Seconds(1));

    // Trigger a monitoring on HangWatcher thread and verify results.
    hang_watcher.TriggerSynchronousMonitoring();

    // Hang is detected since the new WatchHangsInScope temporarily
    // re-activated hang_watching.
    EXPECT_EQ(hang_watcher.GetHangCount(), 1);
  }

  // Trigger a monitoring on HangWatcher thread and verify results.
  hang_watcher.TriggerSynchronousMonitoring();

  // No new hang is detected since execution is back to being covered by
  // |expires_instantly| for which expectations were invalidated.
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

TEST_F(HangWatcherTest,
       NewSeparateWatchHangsInScopeAfterInvalidationDetectsHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  {
    WatchHangsInScope expires_instantly(base::TimeDelta{});
    task_environment_.FastForwardBy(base::Seconds(1));

    // De-activate hang watching.
    base::HangWatcher::InvalidateActiveExpectations();
  }

  WatchHangsInScope also_expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));

  // Trigger a monitoring on HangWatcher thread and verify results.
  hang_watcher.TriggerSynchronousMonitoring();

  // Hang is detected since the new WatchHangsInScope did not have its
  // expectations invalidated.
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

// Test that invalidating expectations from inner WatchHangsInScope will also
// prevent hang detection in outer scopes.
TEST_F(HangWatcherTest, ScopeDisabledObjectInnerScope) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // Start a WatchHangsInScope that expires right away. Then advance
  // time to make sure no hang is detected.
  WatchHangsInScope expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));
  {
    WatchHangsInScope also_expires_instantly(base::TimeDelta{});

    // De-activate hang watching.
    base::HangWatcher::InvalidateActiveExpectations();
    task_environment_.FastForwardBy(base::Seconds(1));
  }

  // Trigger a monitoring on HangWatcher thread and verify results.
  hang_watcher.TriggerSynchronousMonitoring();

  // Hang is ignored since it concerns a scope for which one of the inner scope
  // was ignored.
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}

TEST_F(HangWatcherTest, NewScopeAfterDisabling) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // Start a WatchHangsInScope that expires right away. Then advance
  // time to make sure no hang is detected.
  WatchHangsInScope expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));
  {
    WatchHangsInScope also_expires_instantly(base::TimeDelta{});

    // De-activate hang watching.
    base::HangWatcher::InvalidateActiveExpectations();
    task_environment_.FastForwardBy(base::Seconds(1));
  }

  // New scope for which expectations are never invalidated.
  WatchHangsInScope also_expires_instantly(base::TimeDelta{});
  task_environment_.FastForwardBy(base::Seconds(1));

  // Trigger a monitoring on HangWatcher thread and verify results.
  hang_watcher.TriggerSynchronousMonitoring();

  // Hang is detected because it's unrelated to the hangs that were disabled.
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

TEST_F(HangWatcherTest, NestedScopes) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Create a state object for the test thread since this test is single
  // threaded.
  auto current_hang_watch_state =
      base::internal::HangWatchState::CreateHangWatchStateForCurrentThread(
          HangWatcher::ThreadType::kMainThread);

  ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
  base::TimeTicks original_deadline = current_hang_watch_state->GetDeadline();

  constexpr base::TimeDelta kFirstTimeout(base::Milliseconds(500));
  base::TimeTicks first_deadline = base::TimeTicks::Now() + kFirstTimeout;

  constexpr base::TimeDelta kSecondTimeout(base::Milliseconds(250));
  base::TimeTicks second_deadline = base::TimeTicks::Now() + kSecondTimeout;

  // At this point we have not set any timeouts.
  {
    // Create a first timeout which is more restrictive than the default.
    WatchHangsInScope first_scope(kFirstTimeout);

    // We are on mock time. There is no time advancement and as such no hangs.
    EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
    EXPECT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
    {
      // Set a yet more restrictive deadline. Still no hang.
      WatchHangsInScope second_scope(kSecondTimeout);
      EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
      EXPECT_EQ(current_hang_watch_state->GetDeadline(), second_deadline);
    }
    // First deadline we set should be restored.
    EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
    EXPECT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
  }

  // Original deadline should now be restored.
  EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
  EXPECT_EQ(current_hang_watch_state->GetDeadline(), original_deadline);
}

// Checks that histograms are recorded on the right threads for the browser
// process.
TEST_F(HangWatcherTest, HistogramsLoggedOnBrowserProcessHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  base::HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start blocked threads for all thread types and simulate hangs.
  BlockedThreadsForAllTypes threads(/*timeout=*/base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // Check that histograms are only recorded for the expected threads.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix(
          "HangWatcher.IsThreadHung.BrowserProcess"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Normal",
               BucketsAre(Bucket(true, /*count=*/1)))));
}

TEST_F(HangWatcherTest, HistogramsLoggedOnGpuProcessHang) {
  ScopedFeatureList enable_gpu_watcher(kEnableHangWatcherOnGpuProcess);
  HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);

  // Start blocked threads for all thread types and simulate hangs.
  BlockedThreadsForAllTypes threads(/*timeout=*/base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // Check that histograms are only recorded for the expected threads.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(histogram_tester.GetAllSamplesForPrefix(
                  "HangWatcher.IsThreadHung.GpuProcess"),
              UnorderedElementsAre(
                  Pair("HangWatcher.IsThreadHung.GpuProcess.MainThread",
                       BucketsAre(Bucket(true, /*count=*/1))),
                  Pair("HangWatcher.IsThreadHung.GpuProcess.IOThread",
                       BucketsAre(Bucket(true, /*count=*/1))),
                  Pair("HangWatcher.IsThreadHung.GpuProcess.CompositorThread",
                       BucketsAre(Bucket(true, /*count=*/1)))));
}

struct AnyCriticalTestParam {
  std::string test_name;
  HangWatcher::ProcessType process_type;
  HangWatcher::ThreadType thread_type;
  bool is_critical;
};

// Spot check critical and non-critical process and thread types. We can't do a
// full cross product because some processes types don't support some thread
// types.
using HangWatcherAnyCriticalThreadTests = TestWithParam<AnyCriticalTestParam>;
INSTANTIATE_TEST_SUITE_P(
    CriticalProcessAndThreadSpotChecks,
    HangWatcherAnyCriticalThreadTests,
    ValuesIn<AnyCriticalTestParam>({
        // Test at least one critical thread per process types:
        {.test_name = "BrowserProcessIsCritical",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .thread_type = HangWatcher::ThreadType::kMainThread,
         .is_critical = true},
        {.test_name = "RendererProcessIsCritical",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .thread_type = HangWatcher::ThreadType::kMainThread,
         .is_critical = true},
        {.test_name = "UtilityProcessIsCritical",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .thread_type = HangWatcher::ThreadType::kMainThread,
         .is_critical = true},
        {.test_name = "GpuProcessIsCritical",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .thread_type = HangWatcher::ThreadType::kMainThread,
         .is_critical = true},
        // Test each critical thread types for one process type:
        {.test_name = "MainThreadIsCritical",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .thread_type = HangWatcher::ThreadType::kMainThread,
         .is_critical = true},
        {.test_name = "IOThreadIsCritical",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .thread_type = HangWatcher::ThreadType::kIOThread,
         .is_critical = true},
        {.test_name = "CompositorThreadIsCritical",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .thread_type = HangWatcher::ThreadType::kCompositorThread,
         .is_critical = true},
        // Test non critical threads:
        {.test_name = "ThreadPoolIsNotCritical",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .thread_type = HangWatcher::ThreadType::kThreadPoolThread,
         .is_critical = false},
    }),
    [](const auto& info) { return info.param.test_name; });

// Checks that Any and AnyCritical are correctly recorded for different process
// and thread types.
// TODO(crbug.com/446160865): Re-enable this test.
#if BUILDFLAG(IS_IOS)
#define MAYBE_AnyCriticalThreadHung DISABLED_AnyCriticalThreadHung
#else
#define MAYBE_AnyCriticalThreadHung AnyCriticalThreadHung
#endif
TEST_P(HangWatcherAnyCriticalThreadTests, MAYBE_AnyCriticalThreadHung) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ScopedFeatureList enable_gpu_hang_watcher(kEnableHangWatcherOnGpuProcess);
  SingleThreadTaskEnvironment task_env(TaskEnvironment::TimeSource::MOCK_TIME);
  base::HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(GetParam().process_type);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(GetParam().thread_type, base::Seconds(10));
  task_env.FastForwardBy(base::Seconds(11));

  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung.Any"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.Any",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.AnyCritical",
               BucketsAre(Bucket(GetParam().is_critical, /*count=*/1)))));
}

// Checks that only a single Any/AnyCritical histogram is recorded even if
// multiple threads hang.
TEST_F(HangWatcherTest, AnyRecordedOnlyOnceEvenIfMultipleThreadsHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
  base::HistogramTester histogram_tester;

  // Start and hang multiple threads.
  BlockedThread main(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  BlockedThread io(HangWatcher::ThreadType::kIOThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // A single Any/AnyCritical should be recorded, even if multiple threads hung.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung.Any"),
      UnorderedElementsAre(Pair("HangWatcher.IsThreadHung.Any",
                                BucketsAre(Bucket(true, /*count=*/1))),
                           Pair("HangWatcher.IsThreadHung.AnyCritical",
                                BucketsAre(Bucket(true, /*count=*/1)))));
}

// Checks that histograms with `false` buckets are recorded if there's no hang.
TEST_F(HangWatcherTest, HistogramsLoggedWithoutHangs) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  base::HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start a blocked thread with a 10 seconds hang limit, but don't fastforward
  // time.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));

  // No hang to catch so nothing is recorded.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);

  // A thread of type ThreadForTesting was monitored but didn't hang. This is
  // logged.
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
               BucketsAre(Bucket(false, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.Any",
               BucketsAre(Bucket(false, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.AnyCritical",
               BucketsAre(Bucket(false, /*count=*/1)))));
}

// Histograms should be recorded on each monitoring.
TEST_F(HangWatcherTest, HistogramsLoggedOnEachHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  base::HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // First monitoring catches the hang and emits the histogram.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.Any",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.AnyCritical",
               BucketsAre(Bucket(true, /*count=*/1)))));

  // Hang is logged again even if it would not trigger a crash dump.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
               BucketsAre(Bucket(true, /*count=*/2))),
          Pair("HangWatcher.IsThreadHung.Any",
               BucketsAre(Bucket(true, /*count=*/2))),
          Pair("HangWatcher.IsThreadHung.AnyCritical",
               BucketsAre(Bucket(true, /*count=*/2)))));
}

// Checks that the browser process emits Shutdown histograms on shutdown.
TEST_F(HangWatcherTest, HistogramsLoggedWithShutdownFlag) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  base::HistogramTester histogram_tester;
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start blocked threads for all thread types and simulate hangs.
  BlockedThreadsForAllTypes threads(/*timeout=*/base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // Make this process emit *.Shutdown instead of *.Normal histograms.
  base::HangWatcher::SetShuttingDown();

  // Check that histograms are only recorded for the expected threads.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix(
          "HangWatcher.IsThreadHung.BrowserProcess"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Shutdown",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Shutdown",
               BucketsAre(Bucket(true, /*count=*/1)))));
}

// Parameterized test for validating log-level feature params.
struct HangWatcherLogLevelTestParam {
  std::string test_name;
  HangWatcher::ProcessType process_type;
  std::vector<FeatureRefAndParams> enabled_features;
  bool emit_crashes = false;
  int expected_hang_count;
};
using HangWatcherLogLevelTest = TestWithParam<HangWatcherLogLevelTestParam>;
INSTANTIATE_TEST_SUITE_P(
    LogLevels,
    HangWatcherLogLevelTest,
    ValuesIn<HangWatcherLogLevelTestParam>({
        // Browser process.
        {.test_name = "BrowserCrashReportsEnabledByDefaultIfEmitCrashTrue",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(kEnableHangWatcher, {})},
         .emit_crashes = true,
         .expected_hang_count = 1},
        {.test_name = "BrowserCrashReportsDisabledByDefault",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(kEnableHangWatcher, {})},
         .expected_hang_count = 0},
        {.test_name = "BrowserCrashReportsDisabledAtLogLevel1",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kBrowserProcessUiThreadLogLevelParam, "1"}})},
         .expected_hang_count = 0},
        {.test_name = "BrowserCrashReportsEnabledForUiThread",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kBrowserProcessUiThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "BrowserCrashReportsEnabledForIoThread",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kBrowserProcessIoThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "BrowserCrashReportsAlwaysDisabledForThreadPoolThreads",
         .process_type = HangWatcher::ProcessType::kBrowserProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kBrowserProcessThreadPoolLogLevelParam, "2"}})},
         .expected_hang_count = 1},

        // GPU process.
        {.test_name = "GpuCrashReportsDisabledByDefault",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features =
             {FeatureRefAndParams(kEnableHangWatcherOnGpuProcess, {})},
         .expected_hang_count = 0},
        {.test_name = "GpuCrashReportsDisabledAtLogLevel1",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcherOnGpuProcess,
             {{kGpuProcessMainThreadLogLevelParam, "1"}})},
         .expected_hang_count = 0},
        {.test_name = "GpuCrashReportsEnabledForMainThread",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcherOnGpuProcess,
             {{kGpuProcessMainThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "GpuCrashReportsEnabledForIoThread",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcherOnGpuProcess,
             {{kGpuProcessIoThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "GpuCrashReportsEnabledForCompositorThread",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcherOnGpuProcess,
             {{kGpuProcessCompositorThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "GpuCrashReportsEnabledForThreadPoolThreads",
         .process_type = HangWatcher::ProcessType::kGPUProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcherOnGpuProcess,
             {{kGpuProcessThreadPoolLogLevelParam, "2"}})},
         .expected_hang_count = 1},

        // Renderer process.
        {.test_name = "RendererCrashReportsDisabledByDefault",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(kEnableHangWatcher, {})},
         .expected_hang_count = 0},
        {.test_name = "RendererCrashReportsDisabledAtLogLevel1",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kRendererProcessMainThreadLogLevelParam, "1"}})},
         .expected_hang_count = 0},
        {.test_name = "RendererCrashReportsEnabledForMainThread",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kRendererProcessMainThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "RendererCrashReportsEnabledForIoThread",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kRendererProcessIoThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "RendererCrashReportsEnabledForCompositorThread",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kRendererProcessCompositorThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "RendererCrashReportsEnabledForThreadPoolThreads",
         .process_type = HangWatcher::ProcessType::kRendererProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kRendererProcessThreadPoolLogLevelParam, "2"}})},
         .expected_hang_count = 1},

        // Utility process.
        {.test_name = "UtilityCrashReportsDisabledByDefault",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .enabled_features = {FeatureRefAndParams(kEnableHangWatcher, {})},
         .expected_hang_count = 0},
        {.test_name = "UtilityCrashReportsDisabledAtLogLevel1",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kUtilityProcessMainThreadLogLevelParam, "1"}})},
         .expected_hang_count = 0},
        {.test_name = "UtilityCrashReportsEnabledForMainThread",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kUtilityProcessMainThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "UtilityCrashReportsEnabledForIoThread",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kUtilityProcessIoThreadLogLevelParam, "2"}})},
         .expected_hang_count = 1},
        {.test_name = "UtilityCrashReportsEnabledForThreadPoolThreads",
         .process_type = HangWatcher::ProcessType::kUtilityProcess,
         .enabled_features = {FeatureRefAndParams(
             kEnableHangWatcher,
             {{kUtilityProcessThreadPoolLogLevelParam, "2"}})},
         .expected_hang_count = 1},
    }),
    [](const auto& info) { return info.param.test_name; });

// Tests that log level can be controlled via feature params.
TEST_P(HangWatcherLogLevelTest, CrashLogLevels) {
  SingleThreadTaskEnvironment task_env(TaskEnvironment::TimeSource::MOCK_TIME);
  ScopedFeatureList enable_hang_watcher;
  enable_hang_watcher.InitWithFeaturesAndParameters(GetParam().enabled_features,
                                                    {});
  ManualHangWatcher hang_watcher(GetParam().process_type,
                                 GetParam().emit_crashes);

  ASSERT_TRUE(hang_watcher.IsEnabled());

  // Start blocked threads for all thread types and simulate hangs.
  BlockedThreadsForAllTypes threads(base::Seconds(10));
  task_env.FastForwardBy(base::Seconds(11));

  // Hang reports are enabled when the log level is set to 2.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), GetParam().expected_hang_count);
}

// Test that hangs get recorded for the browser process.
TEST_F(HangWatcherTest, Hang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // First monitoring catches and records the hang.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

// Tests that hangs don't get recorded for the GPU process by default.
TEST_F(HangWatcherTest, GpuProcessHangReportingDisabledByDefault) {
  ScopedFeatureList enable_gpu_watcher(kEnableHangWatcherOnGpuProcess);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // Hang reports are disabled by default on the GPU process.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}

// Tests that hang detection can be enabled on the GPU process.
TEST_F(HangWatcherTest, GpuProcessHangReportingCanBeEnabled) {
  ScopedFeatureList enable_hang_watcher;
  enable_hang_watcher.InitWithFeaturesAndParameters(
      {{kEnableHangWatcherOnGpuProcess,
        {{kGpuProcessMainThreadLogLevelParam, "2"}}}},
      {});
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // Hang reports are disabled by default on the GPU process.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

// Test that a single hang gets recorded when multiple threads hung.
TEST_F(HangWatcherTest, SingleHangRecordedForMultipleThreads) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  base::HistogramTester histogram_tester;

  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start blocked threads for all thread types and simulate hangs.
  BlockedThreadsForAllTypes threads(base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // A single hang report should be sent, even though two threads hung.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);

  EXPECT_THAT(
      histogram_tester.GetAllSamplesForPrefix(
          "HangWatcher.IsThreadHung.BrowserProcess"),
      UnorderedElementsAre(
          Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
               BucketsAre(Bucket(true, /*count=*/1))),
          Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Normal",
               BucketsAre(Bucket(true, /*count=*/1)))));
}

TEST_F(HangWatcherTest, HangAlreadyRecorded) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start a blocked thread and simulate a hang.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(11));

  // First monitoring catches and records the hang.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);

  // Attempt capture again. Second monitoring does not record a new hang because
  // a hang that was already recorded is still live.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}

TEST_F(HangWatcherTest, NoHang) {
  ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Start a blocked thread with a 10 seconds hang limit, but don't fastforward
  // time.
  BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));

  // No hang to catch so nothing is recorded.
  hang_watcher.TriggerSynchronousMonitoring();
  EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}

class HangWatcherSnapshotTest : public testing::Test {
 protected:
  // Verify that a capture takes place and that at the time of the capture the
  // list of hung thread ids is correct.
  void TestIDList(ManualHangWatcher& hang_watcher, const std::string& id_list) {
    list_of_hung_thread_ids_during_capture_ = id_list;
    task_environment_.AdvanceClock(kSmallCPUQuantum);
    hang_watcher.TriggerSynchronousMonitoring();
    EXPECT_EQ(++reference_capture_count_, hang_watcher.GetHangCount());
  }

  // Verify that even if hang monitoring takes place no hangs are detected.
  void ExpectNoCapture(ManualHangWatcher& hang_watcher) {
    int old_capture_count = hang_watcher.GetHangCount();
    task_environment_.AdvanceClock(kSmallCPUQuantum);
    hang_watcher.TriggerSynchronousMonitoring();
    EXPECT_EQ(old_capture_count, hang_watcher.GetHangCount());
  }

  std::string ConcatenateThreadIds(
      const std::vector<base::PlatformThreadId>& ids) const {
    std::string result;
    constexpr char kSeparator{'|'};

    for (PlatformThreadId id : ids) {
      result += base::NumberToString(id.raw()) + kSeparator;
    }

    return result;
  }

  const PlatformThreadId test_thread_id_ = PlatformThread::CurrentId();

  // This is written to by the test main thread and read from the hang watching
  // thread. It does not need to be protected because access to it is
  // synchronized by always setting before triggering the execution of the
  // reading code through HangWatcher::SignalMonitorEventForTesting().
  std::string list_of_hung_thread_ids_during_capture_;

  // Increases at the same time as |hang_capture_count_| to test that capture
  // actually took place.
  int reference_capture_count_ = 0;

  base::test::ScopedFeatureList feature_list_{base::kEnableHangWatcher};

  // Used exclusively for MOCK_TIME.
  test::SingleThreadTaskEnvironment task_environment_{
      test::TaskEnvironment::TimeSource::MOCK_TIME};
};

// Verify that the hang capture fails when marking a thread for blocking fails.
// This simulates a WatchHangsInScope completing between the time the hang
// was detected and the time it is recorded which would create a non-actionable
// report.
TEST_F(HangWatcherSnapshotTest, NonActionableReport) {
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
  {
    // Start a WatchHangsInScope that expires right away. Ensures that
    // the first monitor will detect a hang.
    WatchHangsInScope expires_instantly(base::TimeDelta{});

    internal::HangWatchState* current_hang_watch_state =
        internal::HangWatchState::GetHangWatchStateForCurrentThread();

    // Simulate the deadline changing concurrently during the capture. This
    // makes the capture fail since marking of the deadline fails.
    ASSERT_NE(current_hang_watch_state->GetDeadline(),
              base::TimeTicks::FromInternalValue(kArbitraryDeadline));
    current_hang_watch_state->GetHangWatchDeadlineForTesting()
        ->SetSwitchBitsClosureForTesting(
            base::BindLambdaForTesting([] { return kArbitraryDeadline; }));

    ExpectNoCapture(hang_watcher);

    // Marking failed.
    EXPECT_FALSE(current_hang_watch_state->IsFlagSet(
        internal::HangWatchDeadline::Flag::kShouldBlockOnHang));

    current_hang_watch_state->GetHangWatchDeadlineForTesting()
        ->ResetSwitchBitsClosureForTesting();
  }
}

TEST_F(HangWatcherSnapshotTest, HungThreadIDs) {
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // During hang capture the list of hung threads should be populated.
  // When hang capture is over the list should be empty.
  hang_watcher.SetOnHangClosure(base::BindLambdaForTesting([&] {
    EXPECT_EQ(hang_watcher.GetHungThreadListCrashKeyForTesting(),
              list_of_hung_thread_ids_during_capture_);
  }));

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  BlockedThread blocked_thread(HangWatcher::ThreadType::kMainThread,
                               /*timeout=*/base::TimeDelta{});
  {
    // Ensure the blocking thread entered the scope before the main thread. This
    // will guarantee an ordering while reporting the list of hung threads.
    task_environment_.AdvanceClock(kSmallCPUQuantum);

    // Start a WatchHangsInScope that expires right away. Ensures that
    // the first monitor will detect a hang. This scope will naturally have a
    // later deadline than the one in |blocked_thread_| since it was created
    // after.
    WatchHangsInScope expires_instantly(base::TimeDelta{});

    // Hung thread list should contain the id the blocking thread and then the
    // id of the test main thread since that is the order of increasing
    // deadline.
    TestIDList(hang_watcher,
               ConcatenateThreadIds({blocked_thread.GetId(), test_thread_id_}));

    // |expires_instantly| and the scope from |blocked_thread| are still live
    // but already recorded so should be ignored.
    ExpectNoCapture(hang_watcher);

    // Unblock and join the thread to close the scope in |blocked_thread|.
    blocked_thread.Unblock();
    blocked_thread.WaitDone();

    // |expires_instantly| is still live but already recorded so should be
    // ignored.
    ExpectNoCapture(hang_watcher);
  }

  // All HangWatchScopeEnables are over. There should be no capture.
  ExpectNoCapture(hang_watcher);

  // Once all recorded scopes are over creating a new one and monitoring will
  // trigger a hang detection.
  WatchHangsInScope expires_instantly(base::TimeDelta{});
  TestIDList(hang_watcher, ConcatenateThreadIds({test_thread_id_}));
}

TEST_F(HangWatcherSnapshotTest, TimeSinceLastSystemPowerResumeCrashKey) {
  ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);

  // Override the capture of hangs. Simulate a crash key capture.
  std::string seconds_since_last_power_resume_crash_key;
  hang_watcher.SetOnHangClosure(base::BindLambdaForTesting([&] {
    seconds_since_last_power_resume_crash_key =
        hang_watcher.GetTimeSinceLastSystemPowerResumeCrashKeyValue();
  }));

  // Register the main test thread for hang watching.
  auto unregister_thread_closure =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  {
    WatchHangsInScope expires_instantly(base::TimeDelta{});
    task_environment_.AdvanceClock(kSmallCPUQuantum);

    hang_watcher.TriggerSynchronousMonitoring();
    EXPECT_EQ(1, hang_watcher.GetHangCount());
    EXPECT_EQ("Never suspended", seconds_since_last_power_resume_crash_key);
  }

  {
    test::ScopedPowerMonitorTestSource power_monitor_source;
    power_monitor_source.Suspend();
    task_environment_.AdvanceClock(kSmallCPUQuantum);

    {
      WatchHangsInScope expires_instantly(base::TimeDelta{});
      task_environment_.AdvanceClock(kSmallCPUQuantum);
      hang_watcher.TriggerSynchronousMonitoring();
      EXPECT_EQ(2, hang_watcher.GetHangCount());
      EXPECT_EQ("Power suspended", seconds_since_last_power_resume_crash_key);
    }

    power_monitor_source.Resume();
    constexpr TimeDelta kAfterResumeTime{base::Seconds(5)};
    task_environment_.AdvanceClock(kAfterResumeTime);

    {
      WatchHangsInScope expires_instantly(base::TimeDelta{});
      hang_watcher.TriggerSynchronousMonitoring();
      EXPECT_EQ(3, hang_watcher.GetHangCount());
      EXPECT_EQ(base::NumberToString(kAfterResumeTime.InSeconds()),
                seconds_since_last_power_resume_crash_key);
    }
  }
}

// Determines how long the HangWatcher will wait between calls to
// Monitor(). Choose a low value so that that successive invocations happens
// fast. This makes tests that wait for monitoring run fast and makes tests that
// expect no monitoring fail fast.
const base::TimeDelta kMonitoringPeriod = base::Milliseconds(1);

// Test if and how often the HangWatcher periodically monitors for hangs.
class HangWatcherPeriodicMonitoringTest : public testing::Test {
 public:
  HangWatcherPeriodicMonitoringTest() {
    hang_watcher_.InitializeOnMainThread(
        HangWatcher::ProcessType::kBrowserProcess, /*emit_crashes=*/true);

    hang_watcher_.SetMonitoringPeriodForTesting(kMonitoringPeriod);
    hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating(
        &WaitableEvent::Signal, base::Unretained(&hang_event_)));

    // HangWatcher uses a TickClock to detect how long it slept in between calls
    // to Monitor(). Override that clock to control its subjective passage of
    // time.
    hang_watcher_.SetTickClockForTesting(&test_clock_);
  }

  void TearDown() override {
    hang_watcher_.UninitializeOnMainThreadForTesting();
  }

 protected:
  // Setup the callback invoked after waiting in HangWatcher to advance the
  // tick clock by the desired time delta.
  void InstallAfterWaitCallback(base::TimeDelta time_delta) {
    hang_watcher_.SetAfterWaitCallbackForTesting(base::BindLambdaForTesting(
        [this, time_delta](base::TimeTicks time_before_wait) {
          test_clock_.Advance(time_delta);
        }));
  }

  base::SimpleTestTickClock test_clock_;

  // Single threaded to avoid ThreadPool WorkerThreads registering. Will run
  // delayed tasks created by the tests.
  test::SingleThreadTaskEnvironment task_environment_;

  HangWatcher hang_watcher_;

  // Signaled when a hang is detected.
  WaitableEvent hang_event_;

  base::ScopedClosureRunner unregister_thread_closure_;
};

// Don't register any threads for hang watching. HangWatcher should not monitor.
TEST_F(HangWatcherPeriodicMonitoringTest,
       NoPeriodicMonitoringWithoutRegisteredThreads) {
  RunLoop run_loop;

  // If a call to HangWatcher::Monitor() takes place the test will instantly
  // fail.
  hang_watcher_.SetAfterMonitorClosureForTesting(
      base::BindLambdaForTesting([&run_loop] {
        ADD_FAILURE() << "Monitoring took place!";
        run_loop.Quit();
      }));

  // Make the HangWatcher tick clock advance by exactly the monitoring period
  // after waiting so it will never detect oversleeping between attempts to call
  // Monitor(). This would inhibit monitoring and make the test pass for the
  // wrong reasons.
  InstallAfterWaitCallback(kMonitoringPeriod);

  hang_watcher_.Start();

  // Unblock the test thread. No thread ever registered after the HangWatcher
  // was created in the test's constructor. No monitoring should have taken
  // place.
  task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  run_loop.Run();

  // NOTE:
  // A lack of calls could technically also be caused by the HangWatcher thread
  // executing too slowly / being descheduled. This is a known limitation.
  // It's expected for |TestTimeouts::tiny_timeout()| to be large enough that
  // this is rare.
}

// During normal execution periodic monitoring should take place.
TEST_F(HangWatcherPeriodicMonitoringTest, PeriodicCallsTakePlace) {
  // HangWatcher::Monitor() will run once right away on thread registration.
  // We want to make sure it runs at a couple more times from being scheduled.
  constexpr int kMinimumMonitorCount = 3;

  RunLoop run_loop;

  // Setup the HangWatcher to unblock run_loop when the Monitor() has been
  // invoked enough times.
  hang_watcher_.SetAfterMonitorClosureForTesting(BarrierClosure(
      kMinimumMonitorCount, base::BindLambdaForTesting([&run_loop] {
        // This should only run if there are threads to watch.
        EXPECT_TRUE(base::FeatureList::IsEnabled(kEnableHangWatcher));

        // Test condition are confirmed, stop monitoring.
        HangWatcher::StopMonitoringForTesting();

        // Unblock the test main thread.
        run_loop.Quit();
      })));

  // Make the HangWatcher tick clock advance by exactly the monitoring period
  // after waiting so it will never detect oversleeping between attempts to call
  // Monitor(). This would inhibit monitoring.
  InstallAfterWaitCallback(kMonitoringPeriod);

  hang_watcher_.Start();

  // Register a thread,
  unregister_thread_closure_ =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // The "after monitor" closure only runs if there are threads to watch.
  if (base::FeatureList::IsEnabled(kEnableHangWatcher)) {
    run_loop.Run();
  }

  // No monitored scope means no possible hangs.
  EXPECT_FALSE(hang_event_.IsSignaled());
}

// If the HangWatcher detects it slept for longer than expected it will not
// monitor.
TEST_F(HangWatcherPeriodicMonitoringTest, NoMonitorOnOverSleep) {
  RunLoop run_loop;

  // If a call to HangWatcher::Monitor() takes place the test will instantly
  // fail.
  hang_watcher_.SetAfterMonitorClosureForTesting(
      base::BindLambdaForTesting([&run_loop] {
        ADD_FAILURE() << "Monitoring took place!";
        run_loop.Quit();
      }));

  // Make the HangWatcher tick clock advance so much after waiting that it will
  // detect oversleeping every time. This will keep it from monitoring.
  InstallAfterWaitCallback(base::Minutes(1));

  hang_watcher_.Start();

  // Register a thread.
  unregister_thread_closure_ =
      HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);

  // Unblock the test thread. All waits were perceived as oversleeping so all
  // monitoring was inhibited.
  task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  run_loop.Run();

  // NOTE: A lack of calls could technically also be caused by the HangWatcher
  // thread executing too slowly / being descheduled. This is a known
  // limitation. It's expected for |TestTimeouts::tiny_timeout()| to be large
  // enough that this happens rarely.
}

class WatchHangsInScopeBlockingTest : public testing::Test {
 public:
  WatchHangsInScopeBlockingTest() {
    HangWatcher::InitializeOnMainThread(
        HangWatcher::ProcessType::kBrowserProcess, /*emit_crashes=*/true);

    hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([&] {
      capture_started_.Signal();
      // Simulate capturing that takes a long time.
      PlatformThread::Sleep(base::Milliseconds(500));

      continue_capture_.Wait();
      completed_capture_ = true;
    }));

    hang_watcher_.SetAfterMonitorClosureForTesting(
        base::BindLambdaForTesting([&] {
          // Simulate monitoring that takes a long time.
          PlatformThread::Sleep(base::Milliseconds(500));
          completed_monitoring_.Signal();
        }));

    // Make sure no periodic monitoring takes place.
    hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta);

    hang_watcher_.Start();

    // Register the test main thread for hang watching.
    unregister_thread_closure_ =
        HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
  }

  void TearDown() override {
    HangWatcher::UninitializeOnMainThreadForTesting();
  }

  void VerifyScopesDontBlock() {
    // Start a WatchHangsInScope that cannot possibly cause a hang to be
    // detected.
    {
      WatchHangsInScope long_scope(kVeryLongDelta);

      // Manually trigger a monitoring.
      hang_watcher_.SignalMonitorEventForTesting();

      // Execution has to continue freely here as no capture is in progress.
    }

    // Monitoring should not be over yet because the test code should execute
    // faster when not blocked.
    EXPECT_FALSE(completed_monitoring_.IsSignaled());

    // Wait for the full monitoring process to be complete. This is to prove
    // that monitoring truly executed and that we raced the signaling.
    completed_monitoring_.Wait();

    // No hang means no capture.
    EXPECT_FALSE(completed_capture_);
  }

 protected:
  base::WaitableEvent capture_started_;
  base::WaitableEvent completed_monitoring_;

  // The HangWatcher waits on this event via the "on hang" closure when a hang
  // is detected.
  base::WaitableEvent continue_capture_;
  bool completed_capture_{false};

  base::test::ScopedFeatureList feature_list_{base::kEnableHangWatcher};
  HangWatcher hang_watcher_;
  base::ScopedClosureRunner unregister_thread_closure_;
};

// Tests that execution is unimpeded by ~WatchHangsInScope() when no capture
// ever takes place.
TEST_F(WatchHangsInScopeBlockingTest, ScopeDoesNotBlocksWithoutCapture) {
  // No capture should take place so |continue_capture_| is not signaled to
  // create a test hang if one ever does.
  VerifyScopesDontBlock();
}

// Test that execution blocks in ~WatchHangsInScope() for a thread under
// watch during the capturing of a hang.
TEST_F(WatchHangsInScopeBlockingTest, ScopeBlocksDuringCapture) {
  // The capture completing is not dependent on any test event. Signal to make
  // sure the test is not blocked.
  continue_capture_.Signal();

  // Start a WatchHangsInScope that expires in the past already. Ensures
  // that the first monitor will detect a hang.
  {
    // Start a WatchHangsInScope that expires right away. Ensures that the
    // first monitor will detect a hang.
    WatchHangsInScope expires_right_away(base::TimeDelta{});

    // Manually trigger a monitoring.
    hang_watcher_.SignalMonitorEventForTesting();

    // Ensure that the hang capturing started.
    capture_started_.Wait();

    // Execution will get stuck in the outer scope because it can't escape
    // ~WatchHangsInScope() if a hang capture is under way.
  }

  // A hang was in progress so execution should have been blocked in
  // BlockWhileCaptureInProgress() until capture finishes.
  EXPECT_TRUE(completed_capture_);
  completed_monitoring_.Wait();

  // Reset expectations
  completed_monitoring_.Reset();
  capture_started_.Reset();
  completed_capture_ = false;

  // Verify that scopes don't block just because a capture happened in the past.
  VerifyScopesDontBlock();
}

#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
// Flaky hangs on arm64 Macs: https://crbug.com/1140207
#define MAYBE_NewScopeDoesNotBlockDuringCapture \
  DISABLED_NewScopeDoesNotBlockDuringCapture
#else
#define MAYBE_NewScopeDoesNotBlockDuringCapture \
  NewScopeDoesNotBlockDuringCapture
#endif

// Test that execution does not block in ~WatchHangsInScope() when the scope
// was created after the start of a capture.
TEST_F(WatchHangsInScopeBlockingTest, MAYBE_NewScopeDoesNotBlockDuringCapture) {
  // Start a WatchHangsInScope that expires right away. Ensures that the
  // first monitor will detect a hang.
  WatchHangsInScope expires_right_away(base::TimeDelta{});

  // Manually trigger a monitoring.
  hang_watcher_.SignalMonitorEventForTesting();

  // Ensure that the hang capturing started.
  capture_started_.Wait();

  // A scope started once a capture is already under way should not block
  // execution.
  { WatchHangsInScope also_expires_right_away(base::TimeDelta{}); }

  // Wait for the new WatchHangsInScope to be destroyed to let the capture
  // finish. If the new scope block waiting for the capture to finish this would
  // create a deadlock and the test would hang.
  continue_capture_.Signal();
}

}  // namespace

namespace internal {
namespace {

// Matcher validating that the specified `HangWatchDeadline` has no flag set.
MATCHER(HasNoFlagSet, /*description=*/"") {
  static constexpr auto kAllFlags =
      base::MakeFixedFlatMap<HangWatchDeadline::Flag, std::string_view>({
          {HangWatchDeadline::Flag::kMinValue, "kMinValue"},
          {HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
           "kIgnoreCurrentWatchHangsInScope"},
          {HangWatchDeadline::Flag::kShouldBlockOnHang, "kShouldBlockOnHang"},
      });

  for (const auto& [flag, description] : kAllFlags) {
    if (arg.IsFlagSet(flag)) {
      *result_listener << "where flag " << description << " is set";
      return false;
    }
  }
  return true;
}

class HangWatchDeadlineTest : public testing::Test {
 protected:
  // Return a flag mask without one of the flags for test purposes. Use to
  // ignore that effect of setting a flag that was just set.
  uint64_t FlagsMinus(uint64_t flags, HangWatchDeadline::Flag flag) {
    return flags & ~(static_cast<uint64_t>(flag));
  }

  HangWatchDeadline deadline_;
};

}  // namespace

// Verify that the extract functions don't mangle any bits.
TEST_F(HangWatchDeadlineTest, BitsPreservedThroughExtract) {
  for (auto bits : {kAllOnes, kAllZeros, kOnesThenZeroes, kZeroesThenOnes}) {
    EXPECT_TRUE((HangWatchDeadline::ExtractFlags(bits) |
                 HangWatchDeadline::ExtractDeadline(bits)) == bits);
  }
}

namespace {

// Verify that setting and clearing a persistent flag works and has no unwanted
// side-effects. Neither the flags nor the deadline change concurrently in this
// test.
TEST_F(HangWatchDeadlineTest, SetAndClearPersistentFlag) {
  ASSERT_THAT(deadline_, HasNoFlagSet());

  // Grab the original values for flags and deadline.
  auto [old_flags, old_deadline] = deadline_.GetFlagsAndDeadline();

  // Set the flag. Operation cannot fail.
  deadline_.SetIgnoreCurrentWatchHangsInScope();

  // Get new flags and deadline.
  auto [new_flags, new_deadline] = deadline_.GetFlagsAndDeadline();

  // Flag was set properly.
  EXPECT_TRUE(HangWatchDeadline::IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, new_flags));

  // No side-effect on deadline.
  EXPECT_EQ(new_deadline, old_deadline);

  // No side-effect on other flags.
  EXPECT_EQ(
      FlagsMinus(new_flags,
                 HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope),
      old_flags);

  // Clear the flag, operation cannot fail.
  deadline_.UnsetIgnoreCurrentWatchHangsInScope();

  // Update new values.
  std::tie(new_flags, new_deadline) = deadline_.GetFlagsAndDeadline();

  // All flags back to original state.
  EXPECT_EQ(new_flags, old_flags);

  // Deadline still unaffected.
  EXPECT_EQ(new_deadline, old_deadline);
}

// Verify setting the TimeTicks value works and has no unwanted side-effects.
TEST_F(HangWatchDeadlineTest, SetDeadline) {
  TimeTicks ticks;

  ASSERT_THAT(deadline_, HasNoFlagSet());
  ASSERT_NE(deadline_.GetDeadline(), ticks);

  // Set the deadline and verify it stuck.
  deadline_.SetDeadline(ticks);
  EXPECT_EQ(deadline_.GetDeadline(), ticks);

  // Only the value was modified, no flags should be set.
  EXPECT_THAT(deadline_, HasNoFlagSet());
}

// Verify that setting a non-persistent flag (kShouldBlockOnHang)
// when the TimeTicks value changed since calling the flag setting
// function fails and has no side-effects.
TEST_F(HangWatchDeadlineTest, SetShouldBlockOnHangDeadlineChanged) {
  ASSERT_THAT(deadline_, HasNoFlagSet());

  auto [flags, deadline] = deadline_.GetFlagsAndDeadline();

  // Simulate value change. Flags are constant.
  const base::TimeTicks new_deadline =
      base::TimeTicks::FromInternalValue(kArbitraryDeadline);
  ASSERT_NE(deadline, new_deadline);
  deadline_.SetSwitchBitsClosureForTesting(
      base::BindLambdaForTesting([] { return kArbitraryDeadline; }));

  // kShouldBlockOnHangs does not persist through value change.
  EXPECT_FALSE(deadline_.SetShouldBlockOnHang(flags, deadline));

  // Flag was not applied.
  EXPECT_FALSE(
      deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));

  // New value that was changed concurrently is preserved.
  EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}

// Verify that clearing a persistent (kIgnoreCurrentWatchHangsInScope) when
// the value changed succeeds and has non side-effects.
TEST_F(HangWatchDeadlineTest, ClearIgnoreHangsDeadlineChanged) {
  ASSERT_THAT(deadline_, HasNoFlagSet());

  auto [flags, deadline] = deadline_.GetFlagsAndDeadline();

  deadline_.SetIgnoreCurrentWatchHangsInScope();
  std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline();
  ASSERT_TRUE(HangWatchDeadline::IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, flags));

  // Simulate deadline change. Flags are constant.
  const base::TimeTicks new_deadline =
      base::TimeTicks::FromInternalValue(kArbitraryDeadline);
  ASSERT_NE(deadline, new_deadline);
  deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([] {
    return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) |
           kArbitraryDeadline;
  }));

  // Clearing kIgnoreHang is unaffected by deadline or flags change.
  deadline_.UnsetIgnoreCurrentWatchHangsInScope();
  EXPECT_FALSE(deadline_.IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));

  // New deadline that was changed concurrently is preserved.
  EXPECT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
  EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}

// Verify that setting a persistent (kIgnoreCurrentWatchHangsInScope) when
// the deadline or flags changed succeeds and has non side-effects.
TEST_F(HangWatchDeadlineTest,
       SetIgnoreCurrentHangWatchScopeEnableDeadlineChanged) {
  ASSERT_THAT(deadline_, HasNoFlagSet());

  auto [flags, deadline] = deadline_.GetFlagsAndDeadline();

  // Simulate deadline change. Flags are constant.
  const base::TimeTicks new_deadline =
      base::TimeTicks::FromInternalValue(kArbitraryDeadline);

  ASSERT_NE(deadline, new_deadline);
  deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([] {
    return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) |
           kArbitraryDeadline;
  }));

  // kIgnoreHang persists through value change.
  deadline_.SetIgnoreCurrentWatchHangsInScope();
  EXPECT_TRUE(deadline_.IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));

  // New deadline and flags that changed concurrently are preserved.
  EXPECT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
  EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}

// Setting a new deadline should wipe flags that a not persistent.
// Persistent flags should not be disturbed.
TEST_F(HangWatchDeadlineTest, SetDeadlineWipesFlags) {
  auto [flags, deadline] = deadline_.GetFlagsAndDeadline();

  ASSERT_TRUE(deadline_.SetShouldBlockOnHang(flags, deadline));
  ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));

  std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline();

  deadline_.SetIgnoreCurrentWatchHangsInScope();
  ASSERT_TRUE(deadline_.IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));

  // Change the deadline.
  deadline_.SetDeadline(TimeTicks{});
  EXPECT_EQ(deadline_.GetDeadline(), TimeTicks{});

  // Verify the persistent flag stuck and the non-persistent one was unset.
  EXPECT_FALSE(
      deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
  EXPECT_TRUE(deadline_.IsFlagSet(
      HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
}

}  // namespace
}  // namespace internal
}  // namespace base