#include "device/gamepad/hid_haptic_gamepad.h"
#include <memory>
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "device/gamepad/hid_writer.h"
#include "device/gamepad/public/mojom/gamepad.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
constexpr uint8_t kReportId = 0x42;
constexpr size_t kReportLength = 5;
constexpr HidHapticGamepad::HapticReportData kHapticReportData = {
0x1234, 0xabcd, kReportId, kReportLength, 1, 3, 16, 0, 0xffff};
constexpr uint8_t kStopVibrationData[] = {kReportId, 0x00, 0x00, 0x00, 0x00};
constexpr uint8_t kStartVibrationData[] = {kReportId, 0xff, 0xff, 0xff, 0x7f};
constexpr double kDurationMillis = 1.0;
constexpr double kNonZeroStartDelayMillis = 1.0;
constexpr double kZeroStartDelayMillis = 0.0;
constexpr double kStrongMagnitude = 1.0;
constexpr double kWeakMagnitude = 0.5;
constexpr double kZeroMagnitude = 0.0;
constexpr base::TimeDelta kPendingTaskDuration =
base::Milliseconds(kDurationMillis);
class FakeHidWriter : public HidWriter {
public:
FakeHidWriter() = default;
~FakeHidWriter() override = default;
size_t WriteOutputReport(base::span<const uint8_t> report) override {
output_reports.emplace_back(report.begin(), report.end());
return report.size_bytes();
}
std::vector<std::vector<uint8_t>> output_reports;
};
class HidHapticGamepadTest : public testing::Test {
public:
HidHapticGamepadTest()
: start_vibration_output_report_(base::ToVector(kStartVibrationData)),
stop_vibration_output_report_(base::ToVector(kStopVibrationData)),
first_callback_count_(0),
second_callback_count_(0),
first_callback_result_(
mojom::GamepadHapticsResult::GamepadHapticsResultError),
second_callback_result_(
mojom::GamepadHapticsResult::GamepadHapticsResultError) {
auto fake_hid_writer = std::make_unique<FakeHidWriter>();
fake_hid_writer_ = fake_hid_writer.get();
gamepad_ = std::make_unique<HidHapticGamepad>(kHapticReportData,
std::move(fake_hid_writer));
}
HidHapticGamepadTest(const HidHapticGamepadTest&) = delete;
HidHapticGamepadTest& operator=(const HidHapticGamepadTest&) = delete;
void TearDown() override { gamepad_->Shutdown(); }
void PostPlayEffect(
double start_delay,
double strong_magnitude,
double weak_magnitude,
mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback) {
gamepad_->PlayEffect(
mojom::GamepadHapticEffectType::GamepadHapticEffectTypeDualRumble,
mojom::GamepadEffectParameters::New(
kDurationMillis, start_delay, strong_magnitude, weak_magnitude,
0, 0),
std::move(callback), base::SingleThreadTaskRunner::GetCurrentDefault());
}
void PostResetVibration(
mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback) {
gamepad_->ResetVibration(std::move(callback),
base::SingleThreadTaskRunner::GetCurrentDefault());
}
void FirstCallback(mojom::GamepadHapticsResult result) {
first_callback_count_++;
first_callback_result_ = result;
}
void SecondCallback(mojom::GamepadHapticsResult result) {
second_callback_count_++;
second_callback_result_ = result;
}
const std::vector<uint8_t> start_vibration_output_report_;
const std::vector<uint8_t> stop_vibration_output_report_;
int first_callback_count_;
int second_callback_count_;
mojom::GamepadHapticsResult first_callback_result_;
mojom::GamepadHapticsResult second_callback_result_;
raw_ptr<FakeHidWriter, DanglingUntriaged> fake_hid_writer_;
std::unique_ptr<HidHapticGamepad> gamepad_;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(HidHapticGamepadTest, PlayEffectTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
PostPlayEffect(kZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(start_vibration_output_report_));
EXPECT_EQ(0, first_callback_count_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
first_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, ResetVibrationTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
PostResetVibration(base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
first_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, ZeroVibrationTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
PostPlayEffect(kZeroStartDelayMillis, kZeroMagnitude, kZeroMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(0, first_callback_count_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
first_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, StartDelayTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
PostPlayEffect(kNonZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(0, first_callback_count_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_,
start_vibration_output_report_));
EXPECT_EQ(0, first_callback_count_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_,
start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
first_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, ZeroStartDelayPreemptionTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
PostPlayEffect(kZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
PostPlayEffect(kZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::SecondCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultPreempted,
first_callback_result_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(1, second_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
second_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, NonZeroStartDelayPreemptionTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
PostPlayEffect(kZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
PostPlayEffect(kNonZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::SecondCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultPreempted,
first_callback_result_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_,
start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
task_environment_.FastForwardBy(kPendingTaskDuration);
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_,
start_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(1, second_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
second_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(HidHapticGamepadTest, ResetVibrationPreemptionTest) {
EXPECT_TRUE(fake_hid_writer_->output_reports.empty());
EXPECT_EQ(0, first_callback_count_);
EXPECT_EQ(0, second_callback_count_);
PostPlayEffect(kZeroStartDelayMillis, kStrongMagnitude, kWeakMagnitude,
base::BindOnce(&HidHapticGamepadTest::FirstCallback,
base::Unretained(this)));
PostResetVibration(base::BindOnce(&HidHapticGamepadTest::SecondCallback,
base::Unretained(this)));
task_environment_.RunUntilIdle();
EXPECT_THAT(fake_hid_writer_->output_reports,
testing::ElementsAre(stop_vibration_output_report_));
EXPECT_EQ(1, first_callback_count_);
EXPECT_EQ(1, second_callback_count_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultPreempted,
first_callback_result_);
EXPECT_EQ(mojom::GamepadHapticsResult::GamepadHapticsResultComplete,
second_callback_result_);
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
}
}