#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;
constexpr TimeDelta kVeryLongDelta{base::Days(365)};
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;
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 {
{
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();
wait_until_entered_scope_.Wait();
}
void Unblock() { unblock_thread_.Signal(); }
void WaitDone() { run_event_.Wait(); }
PlatformThreadId GetId() { return thread_.tid(); }
private:
base::DelegateSimpleThread thread_;
WaitableEvent wait_until_entered_scope_;
WaitableEvent run_event_;
base::WaitableEvent unblock_thread_;
const HangWatcher::ThreadType thread_type_;
const base::TimeDelta timeout_;
};
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:
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);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
base::HangWatcher::InvalidateActiveExpectations();
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}
TEST_F(HangWatcherTest, MultipleInvalidateExpectationsDoNotCancelOut) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
base::HangWatcher::InvalidateActiveExpectations();
base::HangWatcher::InvalidateActiveExpectations();
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}
TEST_F(HangWatcherTest,
DISABLED_NewInnerWatchHangsInScopeAfterInvalidationDetectsHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
base::HangWatcher::InvalidateActiveExpectations();
{
WatchHangsInScope also_expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest,
NewSeparateWatchHangsInScopeAfterInvalidationDetectsHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
{
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
base::HangWatcher::InvalidateActiveExpectations();
}
WatchHangsInScope also_expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest, ScopeDisabledObjectInnerScope) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
{
WatchHangsInScope also_expires_instantly(base::TimeDelta{});
base::HangWatcher::InvalidateActiveExpectations();
task_environment_.FastForwardBy(base::Seconds(1));
}
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}
TEST_F(HangWatcherTest, NewScopeAfterDisabling) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
WatchHangsInScope expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
{
WatchHangsInScope also_expires_instantly(base::TimeDelta{});
base::HangWatcher::InvalidateActiveExpectations();
task_environment_.FastForwardBy(base::Seconds(1));
}
WatchHangsInScope also_expires_instantly(base::TimeDelta{});
task_environment_.FastForwardBy(base::Seconds(1));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest, NestedScopes) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
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;
{
WatchHangsInScope first_scope(kFirstTimeout);
EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
EXPECT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
{
WatchHangsInScope second_scope(kSecondTimeout);
EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
EXPECT_EQ(current_hang_watch_state->GetDeadline(), second_deadline);
}
EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
EXPECT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
}
EXPECT_FALSE(current_hang_watch_state->IsOverDeadline());
EXPECT_EQ(current_hang_watch_state->GetDeadline(), original_deadline);
}
TEST_F(HangWatcherTest, HistogramsLoggedOnBrowserProcessHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
base::HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThreadsForAllTypes threads(base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix(
"HangWatcher.IsThreadHung.BrowserProcess"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Normal",
BucketsAre(Bucket(true, 1)))));
}
TEST_F(HangWatcherTest, HistogramsLoggedOnGpuProcessHang) {
ScopedFeatureList enable_gpu_watcher(kEnableHangWatcherOnGpuProcess);
HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);
BlockedThreadsForAllTypes threads(base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(histogram_tester.GetAllSamplesForPrefix(
"HangWatcher.IsThreadHung.GpuProcess"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.GpuProcess.MainThread",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.GpuProcess.IOThread",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.GpuProcess.CompositorThread",
BucketsAre(Bucket(true, 1)))));
}
struct AnyCriticalTestParam {
std::string test_name;
HangWatcher::ProcessType process_type;
HangWatcher::ThreadType thread_type;
bool is_critical;
};
using HangWatcherAnyCriticalThreadTests = TestWithParam<AnyCriticalTestParam>;
INSTANTIATE_TEST_SUITE_P(
CriticalProcessAndThreadSpotChecks,
HangWatcherAnyCriticalThreadTests,
ValuesIn<AnyCriticalTestParam>({
{.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_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_name = "ThreadPoolIsNotCritical",
.process_type = HangWatcher::ProcessType::kBrowserProcess,
.thread_type = HangWatcher::ThreadType::kThreadPoolThread,
.is_critical = false},
}),
[](const auto& info) { return info.param.test_name; });
#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);
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, 1))),
Pair("HangWatcher.IsThreadHung.AnyCritical",
BucketsAre(Bucket(GetParam().is_critical, 1)))));
}
TEST_F(HangWatcherTest, AnyRecordedOnlyOnceEvenIfMultipleThreadsHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
base::HistogramTester histogram_tester;
BlockedThread main(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
BlockedThread io(HangWatcher::ThreadType::kIOThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung.Any"),
UnorderedElementsAre(Pair("HangWatcher.IsThreadHung.Any",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.AnyCritical",
BucketsAre(Bucket(true, 1)))));
}
TEST_F(HangWatcherTest, HistogramsLoggedWithoutHangs) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
base::HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
BucketsAre(Bucket(false, 1))),
Pair("HangWatcher.IsThreadHung.Any",
BucketsAre(Bucket(false, 1))),
Pair("HangWatcher.IsThreadHung.AnyCritical",
BucketsAre(Bucket(false, 1)))));
}
TEST_F(HangWatcherTest, HistogramsLoggedOnEachHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
base::HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.Any",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.AnyCritical",
BucketsAre(Bucket(true, 1)))));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix("HangWatcher.IsThreadHung"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
BucketsAre(Bucket(true, 2))),
Pair("HangWatcher.IsThreadHung.Any",
BucketsAre(Bucket(true, 2))),
Pair("HangWatcher.IsThreadHung.AnyCritical",
BucketsAre(Bucket(true, 2)))));
}
TEST_F(HangWatcherTest, HistogramsLoggedWithShutdownFlag) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
base::HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThreadsForAllTypes threads(base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
base::HangWatcher::SetShuttingDown();
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_THAT(
histogram_tester.GetAllSamplesForPrefix(
"HangWatcher.IsThreadHung.BrowserProcess"),
UnorderedElementsAre(
Pair("HangWatcher.IsThreadHung.BrowserProcess.UIThread.Shutdown",
BucketsAre(Bucket(true, 1))),
Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Shutdown",
BucketsAre(Bucket(true, 1)))));
}
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>({
{.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},
{.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},
{.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},
{.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; });
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());
BlockedThreadsForAllTypes threads(base::Seconds(10));
task_env.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), GetParam().expected_hang_count);
}
TEST_F(HangWatcherTest, Hang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest, GpuProcessHangReportingDisabledByDefault) {
ScopedFeatureList enable_gpu_watcher(kEnableHangWatcherOnGpuProcess);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}
TEST_F(HangWatcherTest, GpuProcessHangReportingCanBeEnabled) {
ScopedFeatureList enable_hang_watcher;
enable_hang_watcher.InitWithFeaturesAndParameters(
{{kEnableHangWatcherOnGpuProcess,
{{kGpuProcessMainThreadLogLevelParam, "2"}}}},
{});
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kGPUProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest, SingleHangRecordedForMultipleThreads) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
base::HistogramTester histogram_tester;
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThreadsForAllTypes threads(base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
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, 1))),
Pair("HangWatcher.IsThreadHung.BrowserProcess.IOThread.Normal",
BucketsAre(Bucket(true, 1)))));
}
TEST_F(HangWatcherTest, HangAlreadyRecorded) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(11));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 1);
}
TEST_F(HangWatcherTest, NoHang) {
ScopedFeatureList enable_hang_watcher(kEnableHangWatcher);
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
BlockedThread thread(HangWatcher::ThreadType::kMainThread, base::Seconds(10));
hang_watcher.TriggerSynchronousMonitoring();
EXPECT_EQ(hang_watcher.GetHangCount(), 0);
}
class HangWatcherSnapshotTest : public testing::Test {
protected:
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());
}
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();
std::string list_of_hung_thread_ids_during_capture_;
int reference_capture_count_ = 0;
base::test::ScopedFeatureList feature_list_{base::kEnableHangWatcher};
test::SingleThreadTaskEnvironment task_environment_{
test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(HangWatcherSnapshotTest, NonActionableReport) {
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
{
WatchHangsInScope expires_instantly(base::TimeDelta{});
internal::HangWatchState* current_hang_watch_state =
internal::HangWatchState::GetHangWatchStateForCurrentThread();
ASSERT_NE(current_hang_watch_state->GetDeadline(),
base::TimeTicks::FromInternalValue(kArbitraryDeadline));
current_hang_watch_state->GetHangWatchDeadlineForTesting()
->SetSwitchBitsClosureForTesting(
base::BindLambdaForTesting([] { return kArbitraryDeadline; }));
ExpectNoCapture(hang_watcher);
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);
hang_watcher.SetOnHangClosure(base::BindLambdaForTesting([&] {
EXPECT_EQ(hang_watcher.GetHungThreadListCrashKeyForTesting(),
list_of_hung_thread_ids_during_capture_);
}));
auto unregister_thread_closure =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
BlockedThread blocked_thread(HangWatcher::ThreadType::kMainThread,
base::TimeDelta{});
{
task_environment_.AdvanceClock(kSmallCPUQuantum);
WatchHangsInScope expires_instantly(base::TimeDelta{});
TestIDList(hang_watcher,
ConcatenateThreadIds({blocked_thread.GetId(), test_thread_id_}));
ExpectNoCapture(hang_watcher);
blocked_thread.Unblock();
blocked_thread.WaitDone();
ExpectNoCapture(hang_watcher);
}
ExpectNoCapture(hang_watcher);
WatchHangsInScope expires_instantly(base::TimeDelta{});
TestIDList(hang_watcher, ConcatenateThreadIds({test_thread_id_}));
}
TEST_F(HangWatcherSnapshotTest, TimeSinceLastSystemPowerResumeCrashKey) {
ManualHangWatcher hang_watcher(HangWatcher::ProcessType::kBrowserProcess);
std::string seconds_since_last_power_resume_crash_key;
hang_watcher.SetOnHangClosure(base::BindLambdaForTesting([&] {
seconds_since_last_power_resume_crash_key =
hang_watcher.GetTimeSinceLastSystemPowerResumeCrashKeyValue();
}));
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);
}
}
}
const base::TimeDelta kMonitoringPeriod = base::Milliseconds(1);
class HangWatcherPeriodicMonitoringTest : public testing::Test {
public:
HangWatcherPeriodicMonitoringTest() {
hang_watcher_.InitializeOnMainThread(
HangWatcher::ProcessType::kBrowserProcess, true);
hang_watcher_.SetMonitoringPeriodForTesting(kMonitoringPeriod);
hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating(
&WaitableEvent::Signal, base::Unretained(&hang_event_)));
hang_watcher_.SetTickClockForTesting(&test_clock_);
}
void TearDown() override {
hang_watcher_.UninitializeOnMainThreadForTesting();
}
protected:
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_;
test::SingleThreadTaskEnvironment task_environment_;
HangWatcher hang_watcher_;
WaitableEvent hang_event_;
base::ScopedClosureRunner unregister_thread_closure_;
};
TEST_F(HangWatcherPeriodicMonitoringTest,
NoPeriodicMonitoringWithoutRegisteredThreads) {
RunLoop run_loop;
hang_watcher_.SetAfterMonitorClosureForTesting(
base::BindLambdaForTesting([&run_loop] {
ADD_FAILURE() << "Monitoring took place!";
run_loop.Quit();
}));
InstallAfterWaitCallback(kMonitoringPeriod);
hang_watcher_.Start();
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
TEST_F(HangWatcherPeriodicMonitoringTest, PeriodicCallsTakePlace) {
constexpr int kMinimumMonitorCount = 3;
RunLoop run_loop;
hang_watcher_.SetAfterMonitorClosureForTesting(BarrierClosure(
kMinimumMonitorCount, base::BindLambdaForTesting([&run_loop] {
EXPECT_TRUE(base::FeatureList::IsEnabled(kEnableHangWatcher));
HangWatcher::StopMonitoringForTesting();
run_loop.Quit();
})));
InstallAfterWaitCallback(kMonitoringPeriod);
hang_watcher_.Start();
unregister_thread_closure_ =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
if (base::FeatureList::IsEnabled(kEnableHangWatcher)) {
run_loop.Run();
}
EXPECT_FALSE(hang_event_.IsSignaled());
}
TEST_F(HangWatcherPeriodicMonitoringTest, NoMonitorOnOverSleep) {
RunLoop run_loop;
hang_watcher_.SetAfterMonitorClosureForTesting(
base::BindLambdaForTesting([&run_loop] {
ADD_FAILURE() << "Monitoring took place!";
run_loop.Quit();
}));
InstallAfterWaitCallback(base::Minutes(1));
hang_watcher_.Start();
unregister_thread_closure_ =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
class WatchHangsInScopeBlockingTest : public testing::Test {
public:
WatchHangsInScopeBlockingTest() {
HangWatcher::InitializeOnMainThread(
HangWatcher::ProcessType::kBrowserProcess, true);
hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([&] {
capture_started_.Signal();
PlatformThread::Sleep(base::Milliseconds(500));
continue_capture_.Wait();
completed_capture_ = true;
}));
hang_watcher_.SetAfterMonitorClosureForTesting(
base::BindLambdaForTesting([&] {
PlatformThread::Sleep(base::Milliseconds(500));
completed_monitoring_.Signal();
}));
hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta);
hang_watcher_.Start();
unregister_thread_closure_ =
HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
}
void TearDown() override {
HangWatcher::UninitializeOnMainThreadForTesting();
}
void VerifyScopesDontBlock() {
{
WatchHangsInScope long_scope(kVeryLongDelta);
hang_watcher_.SignalMonitorEventForTesting();
}
EXPECT_FALSE(completed_monitoring_.IsSignaled());
completed_monitoring_.Wait();
EXPECT_FALSE(completed_capture_);
}
protected:
base::WaitableEvent capture_started_;
base::WaitableEvent completed_monitoring_;
base::WaitableEvent continue_capture_;
bool completed_capture_{false};
base::test::ScopedFeatureList feature_list_{base::kEnableHangWatcher};
HangWatcher hang_watcher_;
base::ScopedClosureRunner unregister_thread_closure_;
};
TEST_F(WatchHangsInScopeBlockingTest, ScopeDoesNotBlocksWithoutCapture) {
VerifyScopesDontBlock();
}
TEST_F(WatchHangsInScopeBlockingTest, ScopeBlocksDuringCapture) {
continue_capture_.Signal();
{
WatchHangsInScope expires_right_away(base::TimeDelta{});
hang_watcher_.SignalMonitorEventForTesting();
capture_started_.Wait();
}
EXPECT_TRUE(completed_capture_);
completed_monitoring_.Wait();
completed_monitoring_.Reset();
capture_started_.Reset();
completed_capture_ = false;
VerifyScopesDontBlock();
}
#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
#define MAYBE_NewScopeDoesNotBlockDuringCapture \
DISABLED_NewScopeDoesNotBlockDuringCapture
#else
#define MAYBE_NewScopeDoesNotBlockDuringCapture \
NewScopeDoesNotBlockDuringCapture
#endif
TEST_F(WatchHangsInScopeBlockingTest, MAYBE_NewScopeDoesNotBlockDuringCapture) {
WatchHangsInScope expires_right_away(base::TimeDelta{});
hang_watcher_.SignalMonitorEventForTesting();
capture_started_.Wait();
{ WatchHangsInScope also_expires_right_away(base::TimeDelta{}); }
continue_capture_.Signal();
}
}
namespace internal {
namespace {
MATCHER(HasNoFlagSet, "") {
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:
uint64_t FlagsMinus(uint64_t flags, HangWatchDeadline::Flag flag) {
return flags & ~(static_cast<uint64_t>(flag));
}
HangWatchDeadline deadline_;
};
}
TEST_F(HangWatchDeadlineTest, BitsPreservedThroughExtract) {
for (auto bits : {kAllOnes, kAllZeros, kOnesThenZeroes, kZeroesThenOnes}) {
EXPECT_TRUE((HangWatchDeadline::ExtractFlags(bits) |
HangWatchDeadline::ExtractDeadline(bits)) == bits);
}
}
namespace {
TEST_F(HangWatchDeadlineTest, SetAndClearPersistentFlag) {
ASSERT_THAT(deadline_, HasNoFlagSet());
auto [old_flags, old_deadline] = deadline_.GetFlagsAndDeadline();
deadline_.SetIgnoreCurrentWatchHangsInScope();
auto [new_flags, new_deadline] = deadline_.GetFlagsAndDeadline();
EXPECT_TRUE(HangWatchDeadline::IsFlagSet(
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, new_flags));
EXPECT_EQ(new_deadline, old_deadline);
EXPECT_EQ(
FlagsMinus(new_flags,
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope),
old_flags);
deadline_.UnsetIgnoreCurrentWatchHangsInScope();
std::tie(new_flags, new_deadline) = deadline_.GetFlagsAndDeadline();
EXPECT_EQ(new_flags, old_flags);
EXPECT_EQ(new_deadline, old_deadline);
}
TEST_F(HangWatchDeadlineTest, SetDeadline) {
TimeTicks ticks;
ASSERT_THAT(deadline_, HasNoFlagSet());
ASSERT_NE(deadline_.GetDeadline(), ticks);
deadline_.SetDeadline(ticks);
EXPECT_EQ(deadline_.GetDeadline(), ticks);
EXPECT_THAT(deadline_, HasNoFlagSet());
}
TEST_F(HangWatchDeadlineTest, SetShouldBlockOnHangDeadlineChanged) {
ASSERT_THAT(deadline_, HasNoFlagSet());
auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
const base::TimeTicks new_deadline =
base::TimeTicks::FromInternalValue(kArbitraryDeadline);
ASSERT_NE(deadline, new_deadline);
deadline_.SetSwitchBitsClosureForTesting(
base::BindLambdaForTesting([] { return kArbitraryDeadline; }));
EXPECT_FALSE(deadline_.SetShouldBlockOnHang(flags, deadline));
EXPECT_FALSE(
deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}
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));
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;
}));
deadline_.UnsetIgnoreCurrentWatchHangsInScope();
EXPECT_FALSE(deadline_.IsFlagSet(
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
EXPECT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}
TEST_F(HangWatchDeadlineTest,
SetIgnoreCurrentHangWatchScopeEnableDeadlineChanged) {
ASSERT_THAT(deadline_, HasNoFlagSet());
auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
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;
}));
deadline_.SetIgnoreCurrentWatchHangsInScope();
EXPECT_TRUE(deadline_.IsFlagSet(
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
EXPECT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
EXPECT_EQ(deadline_.GetDeadline(), new_deadline);
}
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));
deadline_.SetDeadline(TimeTicks{});
EXPECT_EQ(deadline_.GetDeadline(), TimeTicks{});
EXPECT_FALSE(
deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
EXPECT_TRUE(deadline_.IsFlagSet(
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
}
}
}
}