Files
libvirtualhid/tests/unit/test_virtualhid_control_model.cpp
T

320 lines
14 KiB
C++

/**
* @file tests/unit/test_virtualhid_control_model.cpp
* @brief Unit tests for virtualhid_control model helpers.
*/
// standard includes
#include <array>
#include <cstdint>
#include <string>
#include <vector>
// third-party includes
#include <gtest/gtest.h>
// local includes
#include <virtualhid_control_model.hpp>
namespace {
namespace control = lvh::tools::virtualhid_control;
bool visible_button(
const std::array<bool, control::button_choices.size()> &visible_buttons,
lvh::GamepadButton button
) {
for (std::size_t index = 0; index < control::button_choices.size(); ++index) {
if (control::button_choices[index].button == button) {
return visible_buttons[index];
}
}
return false;
}
lvh::GamepadOutput output(lvh::GamepadOutputKind kind) {
lvh::GamepadOutput value;
value.kind = kind;
return value;
}
} // namespace
TEST(VirtualHidControlModelTest, NamesKnownAndFallbackEnumValues) {
EXPECT_EQ(control::device_type_name(lvh::DeviceType::gamepad), L"gamepad");
EXPECT_EQ(control::device_type_name(lvh::DeviceType::keyboard), L"keyboard");
EXPECT_EQ(control::device_type_name(lvh::DeviceType::mouse), L"mouse");
EXPECT_EQ(control::device_type_name(lvh::DeviceType::touchscreen), L"touchscreen");
EXPECT_EQ(control::device_type_name(lvh::DeviceType::trackpad), L"trackpad");
EXPECT_EQ(control::device_type_name(lvh::DeviceType::pen_tablet), L"pen tablet");
EXPECT_EQ(control::device_type_name(static_cast<lvh::DeviceType>(255)), L"unknown");
EXPECT_EQ(control::node_kind_name(lvh::DeviceNodeKind::input_event), L"input");
EXPECT_EQ(control::node_kind_name(lvh::DeviceNodeKind::joystick), L"joystick");
EXPECT_EQ(control::node_kind_name(lvh::DeviceNodeKind::hidraw), L"hidraw");
EXPECT_EQ(control::node_kind_name(lvh::DeviceNodeKind::sysfs), L"sysfs");
EXPECT_EQ(control::node_kind_name(lvh::DeviceNodeKind::other), L"other");
EXPECT_EQ(control::node_kind_name(static_cast<lvh::DeviceNodeKind>(255)), L"other");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::rumble), L"rumble");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::rgb_led), L"rgb led");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::adaptive_triggers), L"adaptive triggers");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::raw_report), L"raw report");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::trigger_rumble), L"trigger rumble");
EXPECT_EQ(control::output_kind_name(lvh::GamepadOutputKind::player_leds), L"player leds");
EXPECT_EQ(control::output_kind_name(static_cast<lvh::GamepadOutputKind>(255)), L"raw report");
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::unknown), L"unknown");
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::discharging), L"discharging");
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::charging), L"charging");
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::full), L"full");
EXPECT_EQ(
control::battery_state_name(lvh::GamepadBatteryState::voltage_or_temperature_error),
L"voltage/temperature error"
);
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::temperature_error), L"temperature error");
EXPECT_EQ(control::battery_state_name(lvh::GamepadBatteryState::charging_error), L"charging error");
EXPECT_EQ(control::battery_state_name(static_cast<lvh::GamepadBatteryState>(255)), L"unknown");
EXPECT_EQ(control::yes_no(true), L"yes");
EXPECT_EQ(control::yes_no(false), L"no");
}
TEST(VirtualHidControlModelTest, NormalizesPastedLicenseKeys) {
EXPECT_EQ(control::normalized_license_key("test-license-key"), "test-license-key");
EXPECT_EQ(control::normalized_license_key("\r\n test-license-key \t"), "test-license-key");
EXPECT_TRUE(control::normalized_license_key(" \r\n\t").empty());
}
TEST(VirtualHidControlModelTest, MapsProfileChoicesToProfiles) {
for (const auto &choice : control::profile_choices) {
const auto profile = control::profile_for_choice(choice);
ASSERT_TRUE(profile.has_value()) << std::string(choice.id.begin(), choice.id.end());
EXPECT_EQ(profile->device_type, lvh::DeviceType::gamepad);
EXPECT_EQ(profile->gamepad_kind, choice.kind);
EXPECT_FALSE(profile->name.empty());
}
const control::ProfileChoice invalid {
L"invalid",
L"Invalid",
static_cast<lvh::GamepadProfileKind>(255),
lvh::ClientControllerType::unknown,
};
EXPECT_FALSE(control::profile_for_choice(invalid).has_value());
}
TEST(VirtualHidControlModelTest, ExposesGamepadAndMouseDeviceChoices) {
ASSERT_EQ(control::device_type_choices.size(), 2U);
EXPECT_EQ(control::device_type_choices[0].type, lvh::DeviceType::gamepad);
EXPECT_EQ(control::device_type_choices[1].type, lvh::DeviceType::mouse);
ASSERT_EQ(control::mouse_button_choices.size(), 5U);
EXPECT_EQ(control::mouse_button_choices[0].button, lvh::MouseButton::left);
EXPECT_EQ(control::mouse_button_choices[1].button, lvh::MouseButton::middle);
EXPECT_EQ(control::mouse_button_choices[2].button, lvh::MouseButton::right);
EXPECT_EQ(control::mouse_button_choices[3].button, lvh::MouseButton::side);
EXPECT_EQ(control::mouse_button_choices[4].button, lvh::MouseButton::extra);
}
TEST(VirtualHidControlModelTest, MapsKeyboardMouseActionsToEvents) {
using enum control::MouseControlAction;
const auto left = control::mouse_event_for_action(move_left, 25, 120);
EXPECT_EQ(left.kind, lvh::MouseEventKind::relative_motion);
EXPECT_EQ(left.x, -25);
EXPECT_EQ(left.y, 0);
const auto right = control::mouse_event_for_action(move_right, 25, 120);
EXPECT_EQ(right.x, 25);
const auto up = control::mouse_event_for_action(move_up, 25, 120);
EXPECT_EQ(up.y, -25);
const auto down = control::mouse_event_for_action(move_down, 25, 120);
EXPECT_EQ(down.y, 25);
const auto wheel_up_event = control::mouse_event_for_action(wheel_up, 25, 120);
EXPECT_EQ(wheel_up_event.kind, lvh::MouseEventKind::vertical_scroll);
EXPECT_EQ(wheel_up_event.high_resolution_scroll, 120);
const auto wheel_down_event = control::mouse_event_for_action(wheel_down, 25, 120);
EXPECT_EQ(wheel_down_event.high_resolution_scroll, -120);
const auto pan_left_event = control::mouse_event_for_action(pan_left, 25, 120);
EXPECT_EQ(pan_left_event.kind, lvh::MouseEventKind::horizontal_scroll);
EXPECT_EQ(pan_left_event.high_resolution_scroll, -120);
const auto pan_right_event = control::mouse_event_for_action(pan_right, 25, 120);
EXPECT_EQ(pan_right_event.high_resolution_scroll, 120);
}
TEST(VirtualHidControlModelTest, BuildsMomentaryMouseButtonEvents) {
const auto press = control::mouse_button_event(lvh::MouseButton::side, true);
EXPECT_EQ(press.kind, lvh::MouseEventKind::button);
EXPECT_EQ(press.button, lvh::MouseButton::side);
EXPECT_TRUE(press.pressed);
const auto release = control::mouse_button_event(lvh::MouseButton::side, false);
EXPECT_EQ(release.kind, lvh::MouseEventKind::button);
EXPECT_EQ(release.button, lvh::MouseButton::side);
EXPECT_FALSE(release.pressed);
}
TEST(VirtualHidControlModelTest, ConvertsSliderValues) {
EXPECT_EQ(control::axis_to_slider(-2.0F), -control::slider_scale);
EXPECT_EQ(control::axis_to_slider(-0.5F), -50);
EXPECT_EQ(control::axis_to_slider(0.0F), 0);
EXPECT_EQ(control::axis_to_slider(0.5F), 50);
EXPECT_EQ(control::axis_to_slider(2.0F), control::slider_scale);
EXPECT_EQ(control::trigger_to_slider(-1.0F), 0);
EXPECT_EQ(control::trigger_to_slider(0.25F), 25);
EXPECT_EQ(control::trigger_to_slider(1.0F), control::slider_scale);
EXPECT_EQ(control::trigger_to_slider(2.0F), control::slider_scale);
EXPECT_FLOAT_EQ(control::slider_to_float(-25), -0.25F);
EXPECT_FLOAT_EQ(control::slider_to_float(0), 0.0F);
EXPECT_FLOAT_EQ(control::slider_to_float(75), 0.75F);
}
TEST(VirtualHidControlModelTest, MapsBatteryComboChoices) {
for (std::size_t index = 0; index < control::battery_choices.size(); ++index) {
EXPECT_EQ(control::battery_choice_index(control::battery_choices[index].state), static_cast<int>(index));
}
EXPECT_EQ(control::battery_choice_index(static_cast<lvh::GamepadBatteryState>(255)), 0);
}
TEST(VirtualHidControlModelTest, FormatsRawHex) {
EXPECT_EQ(control::raw_hex({}), L"");
EXPECT_EQ(control::raw_hex({0x00, 0x0F, 0xA5, 0xFF}), L"000fa5ff");
}
TEST(VirtualHidControlModelTest, SummarizesProfileFeatures) {
const auto generic = lvh::profiles::generic_gamepad();
const auto dualsense = lvh::profiles::dualsense();
const auto mouse = lvh::profiles::mouse();
EXPECT_TRUE(control::supports_normalized_feedback(generic));
EXPECT_TRUE(control::supports_normalized_feedback(dualsense));
EXPECT_EQ(
control::profile_feature_summary(generic),
L"Features: battery no | rumble yes | trigger rumble no | RGB LED no | player LEDs no | adaptive triggers no | raw output yes"
);
EXPECT_EQ(
control::profile_feature_summary(dualsense),
L"Features: battery yes | rumble yes | trigger rumble no | RGB LED yes | player LEDs no | adaptive triggers yes | raw output yes"
);
EXPECT_EQ(
control::device_feature_summary(mouse),
L"Features: relative motion | five buttons | vertical wheel | horizontal wheel"
);
EXPECT_EQ(control::device_feature_summary(generic), control::profile_feature_summary(generic));
}
TEST(VirtualHidControlModelTest, UpdatesVisibleControlsForProfiles) {
std::array<bool, control::button_choices.size()> visible_buttons {};
auto battery_visible = false;
const auto dualshock4 = lvh::profiles::dualshock4();
EXPECT_TRUE(control::update_visible_controls_for_profile(dualshock4, visible_buttons, battery_visible));
EXPECT_TRUE(visible_button(visible_buttons, lvh::GamepadButton::a));
EXPECT_TRUE(visible_button(visible_buttons, lvh::GamepadButton::touchpad));
EXPECT_FALSE(visible_button(visible_buttons, lvh::GamepadButton::misc1));
EXPECT_FALSE(visible_button(visible_buttons, lvh::GamepadButton::paddle1));
EXPECT_TRUE(battery_visible);
EXPECT_FALSE(control::update_visible_controls_for_profile(dualshock4, visible_buttons, battery_visible));
const auto generic = lvh::profiles::generic_gamepad();
EXPECT_TRUE(control::update_visible_controls_for_profile(generic, visible_buttons, battery_visible));
EXPECT_TRUE(visible_button(visible_buttons, lvh::GamepadButton::misc1));
EXPECT_FALSE(visible_button(visible_buttons, lvh::GamepadButton::touchpad));
EXPECT_FALSE(battery_visible);
}
TEST(VirtualHidControlModelTest, SummarizesOutputState) {
using enum lvh::GamepadOutputKind;
const auto generic = lvh::profiles::generic_gamepad();
const auto dualsense = lvh::profiles::dualsense();
control::OutputState state;
EXPECT_EQ(
control::output_summary(state, generic),
L"Output: no reports received"
);
EXPECT_EQ(control::output_summary(state, dualsense), L"Output: no reports received");
state.outputs.push_back({.sequence = 1, .output = output(raw_report)});
EXPECT_EQ(control::output_summary(state, dualsense), L"Output: reports received");
state.latest_raw_report = output(raw_report);
EXPECT_EQ(control::output_summary(state, dualsense), L"Output: raw report");
state.latest_rumble = output(rumble);
state.latest_rumble->low_frequency_rumble = 10;
state.latest_rumble->high_frequency_rumble = 20;
state.latest_trigger_rumble = output(trigger_rumble);
state.latest_trigger_rumble->left_trigger_rumble = 30;
state.latest_trigger_rumble->right_trigger_rumble = 40;
state.latest_rgb_led = output(rgb_led);
state.latest_rgb_led->red = 1;
state.latest_rgb_led->green = 2;
state.latest_rgb_led->blue = 3;
state.latest_adaptive_triggers = output(adaptive_triggers);
state.latest_adaptive_triggers->adaptive_trigger_flags = 4;
state.latest_player_leds = output(player_leds);
state.latest_player_leds->player_leds = {true, false, true, false};
state.latest_player_leds->flashing_player_leds = {false, true, false, true};
EXPECT_EQ(
control::output_summary(state, dualsense),
L"Output: rumble low=10 high=20 | trigger rumble L=30 R=40 | RGB 1,2,3 | adaptive flags=4 | player LEDs solid=1010 flashing=0101"
);
}
TEST(VirtualHidControlModelTest, RecordsOutputsAndMaintainsLatestSummaryFields) {
control::OutputState state;
auto next_sequence = std::uint64_t {7};
auto rumble = output(lvh::GamepadOutputKind::rumble);
rumble.low_frequency_rumble = 100;
rumble.high_frequency_rumble = 200;
control::record_output(state, rumble, next_sequence, 3);
auto trigger_rumble = output(lvh::GamepadOutputKind::trigger_rumble);
trigger_rumble.left_trigger_rumble = 300;
trigger_rumble.right_trigger_rumble = 400;
control::record_output(state, trigger_rumble, next_sequence, 3);
auto rgb = output(lvh::GamepadOutputKind::rgb_led);
rgb.red = 5;
rgb.green = 6;
rgb.blue = 7;
control::record_output(state, rgb, next_sequence, 3);
auto adaptive = output(lvh::GamepadOutputKind::adaptive_triggers);
adaptive.adaptive_trigger_flags = 8;
control::record_output(state, adaptive, next_sequence, 3);
auto player_leds = output(lvh::GamepadOutputKind::player_leds);
player_leds.player_leds = {true, false, true, false};
control::record_output(state, player_leds, next_sequence, 3);
auto raw = output(lvh::GamepadOutputKind::raw_report);
raw.raw_report = {0x12, 0x34};
control::record_output(state, raw, next_sequence, 3);
ASSERT_EQ(state.outputs.size(), 3U);
EXPECT_EQ(state.outputs.front().sequence, 10U);
EXPECT_EQ(state.outputs.back().sequence, 12U);
EXPECT_EQ(next_sequence, 13U);
ASSERT_TRUE(state.latest_rumble.has_value());
EXPECT_EQ(state.latest_rumble->low_frequency_rumble, 100);
ASSERT_TRUE(state.latest_trigger_rumble.has_value());
EXPECT_EQ(state.latest_trigger_rumble->right_trigger_rumble, 400);
ASSERT_TRUE(state.latest_rgb_led.has_value());
EXPECT_EQ(state.latest_rgb_led->blue, 7);
ASSERT_TRUE(state.latest_adaptive_triggers.has_value());
EXPECT_EQ(state.latest_adaptive_triggers->adaptive_trigger_flags, 8);
ASSERT_TRUE(state.latest_player_leds.has_value());
EXPECT_EQ(state.latest_player_leds->player_leds, (std::array {true, false, true, false}));
ASSERT_TRUE(state.latest_raw_report.has_value());
EXPECT_EQ(state.latest_raw_report->raw_report, (std::vector<std::uint8_t> {0x12, 0x34}));
}