#include "gpu/ipc/service/gpu_watchdog_thread.h"
#include <memory>
#include <string>
#include "base/test/task_environment.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/system/sys_info.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/power_monitor_test.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace gpu {
namespace {
constexpr auto kGpuWatchdogTimeoutForTesting = base::Milliseconds(120);
constexpr auto kExtraGPUJobTimeForTesting = base::Milliseconds(500);
[[maybe_unused]] constexpr auto kGpuWatchdogTimeoutForTestingSlow =
base::Milliseconds(240);
[[maybe_unused]] constexpr auto kExtraGPUJobTimeForTestingSlow =
base::Milliseconds(1000);
[[maybe_unused]] constexpr auto kGpuWatchdogTimeoutForTestingSlowest =
base::Milliseconds(1000);
[[maybe_unused]] constexpr auto kExtraGPUJobTimeForTestingSlowest =
base::Milliseconds(4000);
void SimpleTask(base::TimeDelta duration, base::TimeDelta extra_time) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
auto start_timetick = base::TimeTicks::Now();
do {
} while ((base::TimeTicks::Now() - start_timetick) < duration);
base::PlatformThread::Sleep(extra_time);
#else
base::PlatformThread::Sleep(duration + extra_time);
#endif
}
}
class GpuWatchdogTest : public testing::Test {
public:
GpuWatchdogTest() {}
void LongTaskWithReportProgress(base::TimeDelta duration,
base::TimeDelta report_delta);
#if BUILDFLAG(IS_ANDROID)
void LongTaskFromBackgroundToForeground(
base::TimeDelta duration,
base::TimeDelta extra_time,
base::TimeDelta time_to_switch_to_foreground);
#endif
void SetUp() override;
protected:
~GpuWatchdogTest() override = default;
base::test::SingleThreadTaskEnvironment task_environment_;
base::RunLoop run_loop;
std::unique_ptr<gpu::GpuWatchdogThread> watchdog_thread_;
base::TimeDelta timeout_ = kGpuWatchdogTimeoutForTesting;
base::TimeDelta extra_gpu_job_time_ = kExtraGPUJobTimeForTesting;
base::TimeDelta full_thread_time_on_windows_ = base::TimeDelta();
};
class GpuWatchdogPowerTest : public GpuWatchdogTest {
public:
GpuWatchdogPowerTest() {}
void LongTaskOnResume(base::TimeDelta duration,
base::TimeDelta extra_time,
base::TimeDelta time_to_power_resume);
void SetUp() override;
void TearDown() override;
protected:
~GpuWatchdogPowerTest() override = default;
base::test::ScopedPowerMonitorTestSource power_monitor_source_;
};
void GpuWatchdogTest::SetUp() {
ASSERT_TRUE(base::SingleThreadTaskRunner::HasCurrentDefault());
ASSERT_TRUE(base::CurrentThread::IsSet());
enum TimeOutType {
kNormal,
kSlow,
kSlowest,
};
TimeOutType timeout_type = kNormal;
#if BUILDFLAG(IS_MAC)
std::string model_str = base::SysInfo::HardwareModelName();
std::optional<base::SysInfo::HardwareModelNameSplit> split =
base::SysInfo::SplitHardwareModelNameDoNotUse(model_str);
if (split && split.value().category == "MacBookPro" &&
split.value().model < 14) {
timeout_type = kSlow;
}
#elif BUILDFLAG(IS_ANDROID)
int32_t major_version = 0;
int32_t minor_version = 0;
int32_t bugfix_version = 0;
base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
&bugfix_version);
if (major_version < 9) {
timeout_type = kSlow;
}
#elif BUILDFLAG(IS_FUCHSIA)
timeout_type = kSlowest;
#endif
if (timeout_type == kSlow) {
timeout_ = kGpuWatchdogTimeoutForTestingSlow;
extra_gpu_job_time_ = kExtraGPUJobTimeForTestingSlow;
} else if (timeout_type == kSlowest) {
timeout_ = kGpuWatchdogTimeoutForTestingSlowest;
extra_gpu_job_time_ = kExtraGPUJobTimeForTestingSlowest;
}
#if BUILDFLAG(IS_WIN)
full_thread_time_on_windows_ = timeout_ * kMaxCountOfMoreGpuThreadTimeAllowed;
#endif
watchdog_thread_ = gpu::GpuWatchdogThread::Create(
false,
timeout_,
kRestartFactor,
true, "GpuWatchdog");
}
void GpuWatchdogPowerTest::SetUp() {
GpuWatchdogTest::SetUp();
watchdog_thread_->OnInitComplete();
}
void GpuWatchdogPowerTest::TearDown() {
GpuWatchdogTest::TearDown();
watchdog_thread_.reset();
}
void GpuWatchdogTest::LongTaskWithReportProgress(base::TimeDelta duration,
base::TimeDelta report_delta) {
base::TimeTicks start = base::TimeTicks::Now();
base::TimeTicks end;
do {
SimpleTask(report_delta, base::TimeDelta());
watchdog_thread_->ReportProgress();
end = base::TimeTicks::Now();
} while (end - start <= duration);
}
#if BUILDFLAG(IS_ANDROID)
void GpuWatchdogTest::LongTaskFromBackgroundToForeground(
base::TimeDelta duration,
base::TimeDelta extra_time,
base::TimeDelta time_to_switch_to_foreground) {
watchdog_thread_->OnBackgrounded();
SimpleTask(time_to_switch_to_foreground, base::TimeDelta());
watchdog_thread_->OnForegrounded();
SimpleTask(duration, extra_time);
}
#endif
void GpuWatchdogPowerTest::LongTaskOnResume(
base::TimeDelta duration,
base::TimeDelta extra_time,
base::TimeDelta time_to_power_resume) {
power_monitor_source_.GenerateSuspendEvent();
SimpleTask(time_to_power_resume, base::TimeDelta());
power_monitor_source_.GenerateResumeEvent();
SimpleTask(duration, extra_time);
}
TEST_F(GpuWatchdogTest, GpuInitializationComplete) {
auto normal_task_time = timeout_ / 4;
SimpleTask(normal_task_time, base::TimeDelta());
watchdog_thread_->OnInitComplete();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogTest, GpuInitializationHang) {
auto allowed_time = timeout_ * 2 + full_thread_time_on_windows_;
SimpleTask(allowed_time, extra_gpu_job_time_);
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
TEST_F(GpuWatchdogTest, GpuInitializationAndRunningTasks) {
auto normal_task_time = timeout_ / 4;
SimpleTask(normal_task_time, base::TimeDelta());
watchdog_thread_->OnInitComplete();
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SimpleTask, normal_task_time,
base::TimeDelta()));
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SimpleTask, normal_task_time,
base::TimeDelta()));
auto normal_long_task_time = timeout_ * 6;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&GpuWatchdogTest::LongTaskWithReportProgress,
base::Unretained(this), normal_long_task_time,
timeout_ / 4));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogTest, GpuRunningATaskHang) {
watchdog_thread_->OnInitComplete();
auto allowed_time = timeout_ * 2 + full_thread_time_on_windows_;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, allowed_time, extra_gpu_job_time_));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(GpuWatchdogTest, ChromeInBackground) {
watchdog_thread_->OnBackgrounded();
auto normal_long_task_time = timeout_ * 6;
SimpleTask(normal_long_task_time, base::TimeDelta());
watchdog_thread_->OnInitComplete();
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SimpleTask, normal_long_task_time,
base::TimeDelta()));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogTest, GpuSwitchingToForegroundHang) {
watchdog_thread_->OnInitComplete();
auto allowed_time = timeout_ * (kRestartFactor + 1);
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&GpuWatchdogTest::LongTaskFromBackgroundToForeground,
base::Unretained(this), allowed_time,
extra_gpu_job_time_,
timeout_ / 4));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
#endif
TEST_F(GpuWatchdogTest, GpuInitializationPause) {
SimpleTask(timeout_ / 4,
base::TimeDelta());
watchdog_thread_->PauseWatchdog();
auto normal_long_task_time = timeout_ * 6;
SimpleTask(normal_long_task_time, base::TimeDelta());
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
watchdog_thread_->ResumeWatchdog();
auto allowed_time = timeout_ * 2 + full_thread_time_on_windows_;
SimpleTask(allowed_time, extra_gpu_job_time_);
result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
TEST_F(GpuWatchdogPowerTest, GpuOnSuspend) {
power_monitor_source_.GenerateSuspendEvent();
auto normal_long_task_time = timeout_ * 6;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SimpleTask, normal_long_task_time,
base::TimeDelta()));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogPowerTest, GpuOnResumeHang) {
auto allowed_time =
timeout_ * (kRestartFactor + 1) + full_thread_time_on_windows_;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&GpuWatchdogPowerTest::LongTaskOnResume,
base::Unretained(this),
allowed_time,
extra_gpu_job_time_,
timeout_ / 4));
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
}