/** * @file tests/unit/test_runtime.cpp * @brief Unit tests for runtime and virtual gamepad handles. */ // lib includes #include // local includes #include "fixtures/fixtures.hpp" #if defined(_WIN32) #include "platform/windows/shared/lvh_windows_protocol.h" #endif /** * @brief Test fixture for Linux runtime integration tests. */ class LinuxRuntimeTest: public LinuxTest {}; TEST(RuntimeTest, FakeBackendReportsCapabilities) { auto runtime = lvh::Runtime::create(); EXPECT_EQ(runtime->backend_kind(), lvh::BackendKind::fake); EXPECT_EQ(runtime->capabilities().backend_name, "fake"); EXPECT_TRUE(runtime->capabilities().supports_gamepad); EXPECT_TRUE(runtime->capabilities().supports_keyboard); EXPECT_TRUE(runtime->capabilities().supports_mouse); EXPECT_TRUE(runtime->capabilities().supports_touchscreen); EXPECT_TRUE(runtime->capabilities().supports_trackpad); EXPECT_TRUE(runtime->capabilities().supports_pen_tablet); EXPECT_TRUE(runtime->capabilities().supports_output_reports); EXPECT_FALSE(runtime->capabilities().requires_installed_driver); } TEST(RuntimeTest, PlatformDefaultReportsCurrentPlatformCapabilities) { lvh::RuntimeOptions options; options.backend = lvh::BackendKind::platform_default; auto runtime = lvh::Runtime::create(options); EXPECT_EQ(runtime->backend_kind(), lvh::BackendKind::platform_default); #if defined(__linux__) EXPECT_EQ(runtime->capabilities().backend_name, "linux-uhid-uinput"); EXPECT_FALSE(runtime->capabilities().requires_installed_driver); #elif defined(_WIN32) EXPECT_EQ(runtime->capabilities().backend_name, "windows-umdf"); EXPECT_TRUE(runtime->capabilities().requires_installed_driver); EXPECT_TRUE(runtime->capabilities().supports_keyboard); EXPECT_TRUE(runtime->capabilities().supports_mouse); EXPECT_FALSE(runtime->capabilities().supports_touchscreen); EXPECT_FALSE(runtime->capabilities().supports_trackpad); EXPECT_FALSE(runtime->capabilities().supports_pen_tablet); auto keyboard = runtime->create_keyboard(); ASSERT_TRUE(keyboard) << keyboard.status.message(); EXPECT_TRUE(keyboard.keyboard->close().ok()); auto mouse = runtime->create_mouse(); ASSERT_TRUE(mouse) << mouse.status.message(); EXPECT_TRUE(mouse.mouse->close().ok()); EXPECT_EQ(runtime->create_touchscreen().status.code(), lvh::ErrorCode::unsupported_profile); EXPECT_EQ(runtime->create_trackpad().status.code(), lvh::ErrorCode::unsupported_profile); EXPECT_EQ(runtime->create_pen_tablet().status.code(), lvh::ErrorCode::unsupported_profile); if (!runtime->capabilities().supports_gamepad) { auto created = runtime->create_gamepad(lvh::profiles::xbox_360()); EXPECT_FALSE(created); EXPECT_EQ(created.status.code(), lvh::ErrorCode::backend_unavailable); } else { auto created = runtime->create_gamepad(lvh::profiles::xbox_360()); ASSERT_TRUE(created) << created.status.message(); ASSERT_NE(created.gamepad, nullptr); EXPECT_FALSE(created.gamepad->device_nodes().empty()); lvh::GamepadState state; state.buttons.set(lvh::GamepadButton::a); state.left_stick.x = 0.25F; state.right_trigger = 1.0F; EXPECT_TRUE(created.gamepad->submit(state).ok()); EXPECT_TRUE(created.gamepad->close().ok()); EXPECT_TRUE(created.gamepad->close().ok()); EXPECT_EQ(created.gamepad->submit(state).code(), lvh::ErrorCode::device_closed); } auto invalid_profile = lvh::profiles::xbox_360(); invalid_profile.report_descriptor.resize(LVH_WINDOWS_MAX_REPORT_DESCRIPTOR_SIZE + 1U); EXPECT_EQ(runtime->create_gamepad(invalid_profile).status.code(), lvh::ErrorCode::invalid_argument); invalid_profile = lvh::profiles::xbox_360(); invalid_profile.input_report_size = LVH_WINDOWS_MAX_INPUT_REPORT_SIZE + 1U; EXPECT_EQ(runtime->create_gamepad(invalid_profile).status.code(), lvh::ErrorCode::invalid_argument); invalid_profile = lvh::profiles::xbox_360(); invalid_profile.output_report_size = LVH_WINDOWS_MAX_OUTPUT_REPORT_SIZE + 1U; EXPECT_EQ(runtime->create_gamepad(invalid_profile).status.code(), lvh::ErrorCode::invalid_argument); #else EXPECT_EQ(runtime->capabilities().backend_name, "platform-default-unimplemented"); EXPECT_FALSE(runtime->capabilities().supports_gamepad); auto created = runtime->create_gamepad(lvh::profiles::xbox_360()); EXPECT_FALSE(created); EXPECT_EQ(created.status.code(), lvh::ErrorCode::backend_unavailable); #endif } TEST(RuntimeTest, CreatesSubmitsAndClosesGamepad) { auto runtime = lvh::Runtime::create(); auto created = runtime->create_gamepad(lvh::profiles::xbox_360()); ASSERT_TRUE(created); ASSERT_NE(created.gamepad, nullptr); EXPECT_TRUE(created.gamepad->is_open()); EXPECT_TRUE(created.gamepad->device_nodes().empty()); EXPECT_EQ(runtime->active_device_count(), 1U); lvh::GamepadState state; state.buttons.set(lvh::GamepadButton::b); state.left_stick.x = 2.0F; state.left_trigger = 2.0F; EXPECT_TRUE(created.gamepad->submit(state).ok()); EXPECT_EQ(created.gamepad->submit_count(), 1U); EXPECT_EQ(created.gamepad->last_submitted_state().left_stick.x, 1.0F); EXPECT_EQ(created.gamepad->last_submitted_state().left_trigger, 1.0F); EXPECT_FALSE(created.gamepad->last_input_report().empty()); EXPECT_TRUE(created.gamepad->close().ok()); EXPECT_FALSE(created.gamepad->is_open()); EXPECT_EQ(runtime->active_device_count(), 0U); EXPECT_EQ(created.gamepad->submit(state).code(), lvh::ErrorCode::device_closed); } TEST(RuntimeTest, DispatchesOutputCallback) { auto runtime = lvh::Runtime::create(); auto created = runtime->create_gamepad(lvh::profiles::dualsense()); ASSERT_TRUE(created); lvh::GamepadOutput received; bool was_called = false; created.gamepad->set_output_callback([&received, &was_called](const lvh::GamepadOutput &output) { received = output; was_called = true; }); lvh::GamepadOutput output; output.kind = lvh::GamepadOutputKind::rumble; output.low_frequency_rumble = 123; output.high_frequency_rumble = 456; EXPECT_TRUE(created.gamepad->dispatch_output(output).ok()); EXPECT_TRUE(was_called); EXPECT_EQ(received.kind, lvh::GamepadOutputKind::rumble); EXPECT_EQ(received.low_frequency_rumble, 123); EXPECT_EQ(received.high_frequency_rumble, 456); } TEST(RuntimeTest, CreatesSubmitsAndClosesKeyboard) { auto runtime = lvh::Runtime::create(); auto created = runtime->create_keyboard(); ASSERT_TRUE(created); ASSERT_NE(created.keyboard, nullptr); EXPECT_TRUE(created.keyboard->is_open()); EXPECT_TRUE(created.keyboard->device_nodes().empty()); EXPECT_EQ(created.keyboard->profile().device_type, lvh::DeviceType::keyboard); EXPECT_EQ(runtime->active_device_count(), 1U); EXPECT_TRUE(created.keyboard->press(0x41).ok()); EXPECT_EQ(created.keyboard->submit_count(), 1U); EXPECT_EQ(created.keyboard->last_submitted_event().key_code, 0x41); EXPECT_TRUE(created.keyboard->last_submitted_event().pressed); EXPECT_TRUE(created.keyboard->release(0x41).ok()); EXPECT_TRUE(created.keyboard->type_text({.text = "A"}).ok()); EXPECT_EQ(created.keyboard->submit_count(), 3U); EXPECT_EQ(created.keyboard->press(0).code(), lvh::ErrorCode::invalid_argument); EXPECT_TRUE(created.keyboard->close().ok()); EXPECT_FALSE(created.keyboard->is_open()); EXPECT_EQ(runtime->active_device_count(), 0U); EXPECT_EQ(created.keyboard->press(0x41).code(), lvh::ErrorCode::device_closed); } TEST(RuntimeTest, CreatesSubmitsAndClosesMouse) { auto runtime = lvh::Runtime::create(); auto created = runtime->create_mouse(); ASSERT_TRUE(created); ASSERT_NE(created.mouse, nullptr); EXPECT_TRUE(created.mouse->is_open()); EXPECT_TRUE(created.mouse->device_nodes().empty()); EXPECT_EQ(created.mouse->profile().device_type, lvh::DeviceType::mouse); EXPECT_EQ(runtime->active_device_count(), 1U); EXPECT_TRUE(created.mouse->move_relative(10, -5).ok()); EXPECT_EQ(created.mouse->submit_count(), 1U); EXPECT_EQ(created.mouse->last_submitted_event().kind, lvh::MouseEventKind::relative_motion); EXPECT_EQ(created.mouse->last_submitted_event().x, 10); EXPECT_EQ(created.mouse->last_submitted_event().y, -5); EXPECT_TRUE(created.mouse->move_absolute(100, 200, 1920, 1080).ok()); EXPECT_TRUE(created.mouse->button(lvh::MouseButton::right, true).ok()); EXPECT_TRUE(created.mouse->vertical_scroll(120).ok()); EXPECT_TRUE(created.mouse->horizontal_scroll(-120).ok()); EXPECT_EQ(created.mouse->submit_count(), 5U); EXPECT_EQ(created.mouse->move_absolute(1, 1, 0, 0).code(), lvh::ErrorCode::invalid_argument); EXPECT_TRUE(created.mouse->close().ok()); EXPECT_FALSE(created.mouse->is_open()); EXPECT_EQ(runtime->active_device_count(), 0U); EXPECT_EQ(created.mouse->move_relative(1, 1).code(), lvh::ErrorCode::device_closed); } TEST(RuntimeTest, CreatesSubmitsAndClosesTouchDevices) { auto runtime = lvh::Runtime::create(); auto touchscreen = runtime->create_touchscreen(); ASSERT_TRUE(touchscreen); ASSERT_NE(touchscreen.touchscreen, nullptr); EXPECT_EQ(touchscreen.touchscreen->profile().device_type, lvh::DeviceType::touchscreen); lvh::TouchContact contact { .id = 1, .x = 0.5F, .y = 0.25F, .pressure = 1.0F, .orientation = 10, }; EXPECT_TRUE(touchscreen.touchscreen->place_contact(contact).ok()); EXPECT_TRUE(touchscreen.touchscreen->release_contact(contact.id).ok()); EXPECT_EQ(touchscreen.touchscreen->submit_count(), 2U); EXPECT_TRUE(touchscreen.touchscreen->close().ok()); EXPECT_EQ(touchscreen.touchscreen->place_contact(contact).code(), lvh::ErrorCode::device_closed); auto trackpad = runtime->create_trackpad(); ASSERT_TRUE(trackpad); ASSERT_NE(trackpad.trackpad, nullptr); EXPECT_EQ(trackpad.trackpad->profile().device_type, lvh::DeviceType::trackpad); EXPECT_TRUE(trackpad.trackpad->place_contact(contact).ok()); EXPECT_TRUE(trackpad.trackpad->button(true).ok()); EXPECT_TRUE(trackpad.trackpad->release_contact(contact.id).ok()); EXPECT_EQ(trackpad.trackpad->submit_count(), 3U); EXPECT_TRUE(trackpad.trackpad->close().ok()); EXPECT_EQ(trackpad.trackpad->button(false).code(), lvh::ErrorCode::device_closed); } TEST(RuntimeTest, CreatesSubmitsAndClosesPenTablet) { auto runtime = lvh::Runtime::create(); auto created = runtime->create_pen_tablet(); ASSERT_TRUE(created); ASSERT_NE(created.pen_tablet, nullptr); EXPECT_EQ(created.pen_tablet->profile().device_type, lvh::DeviceType::pen_tablet); lvh::PenToolState tool { .tool = lvh::PenToolType::pen, .x = 0.25F, .y = 0.5F, .pressure = 0.75F, .distance = -1.0F, .tilt_x = 45.0F, .tilt_y = -45.0F, }; EXPECT_TRUE(created.pen_tablet->place_tool(tool).ok()); EXPECT_TRUE(created.pen_tablet->button(lvh::PenButton::primary, true).ok()); EXPECT_EQ(created.pen_tablet->submit_count(), 2U); EXPECT_EQ(created.pen_tablet->last_submitted_tool().tool, lvh::PenToolType::pen); EXPECT_TRUE(created.pen_tablet->close().ok()); EXPECT_EQ(created.pen_tablet->button(lvh::PenButton::primary, false).code(), lvh::ErrorCode::device_closed); } TEST_F(LinuxRuntimeTest, LinuxUhidSmokeTestRequiresPrerequisites) { ASSERT_TRUE(HasReadableWritableDeviceNode("/dev/uhid")); lvh::RuntimeOptions options; options.backend = lvh::BackendKind::platform_default; auto runtime = lvh::Runtime::create(options); ASSERT_TRUE(runtime->capabilities().supports_gamepad); auto created = runtime->create_gamepad(lvh::profiles::xbox_360()); ASSERT_TRUE(created) << created.status.message(); lvh::GamepadState state; state.buttons.set(lvh::GamepadButton::a); state.left_stick.x = 0.25F; state.right_trigger = 1.0F; EXPECT_TRUE(created.gamepad->submit(state).ok()); EXPECT_TRUE(created.gamepad->close().ok()); } TEST_F(LinuxRuntimeTest, LinuxUinputSmokeTestRequiresPrerequisites) { ASSERT_TRUE(HasReadableWritableDeviceNode("/dev/uinput")); lvh::RuntimeOptions options; options.backend = lvh::BackendKind::platform_default; auto runtime = lvh::Runtime::create(options); const auto &capabilities = runtime->capabilities(); ASSERT_TRUE(capabilities.supports_keyboard); auto keyboard = runtime->create_keyboard(); ASSERT_TRUE(keyboard) << keyboard.status.message(); EXPECT_TRUE(keyboard.keyboard->press(0x41).ok()); EXPECT_TRUE(keyboard.keyboard->release(0x41).ok()); ASSERT_TRUE(capabilities.supports_mouse); auto mouse = runtime->create_mouse(); ASSERT_TRUE(mouse) << mouse.status.message(); EXPECT_TRUE(mouse.mouse->move_relative(1, 1).ok()); EXPECT_TRUE(mouse.mouse->vertical_scroll(120).ok()); ASSERT_TRUE(capabilities.supports_touchscreen); auto touchscreen = runtime->create_touchscreen(); ASSERT_TRUE(touchscreen) << touchscreen.status.message(); EXPECT_TRUE(touchscreen.touchscreen->place_contact({.id = 1, .x = 0.5F, .y = 0.25F, .pressure = 1.0F}).ok()); EXPECT_TRUE(touchscreen.touchscreen->release_contact(1).ok()); ASSERT_TRUE(capabilities.supports_trackpad); auto trackpad = runtime->create_trackpad(); ASSERT_TRUE(trackpad) << trackpad.status.message(); EXPECT_TRUE(trackpad.trackpad->place_contact({.id = 1, .x = 0.5F, .y = 0.25F, .pressure = 1.0F}).ok()); EXPECT_TRUE(trackpad.trackpad->button(true).ok()); EXPECT_TRUE(trackpad.trackpad->button(false).ok()); EXPECT_TRUE(trackpad.trackpad->release_contact(1).ok()); ASSERT_TRUE(capabilities.supports_pen_tablet); auto pen_tablet = runtime->create_pen_tablet(); ASSERT_TRUE(pen_tablet) << pen_tablet.status.message(); EXPECT_TRUE( pen_tablet.pen_tablet ->place_tool({.tool = lvh::PenToolType::pen, .x = 0.5F, .y = 0.25F, .pressure = 0.75F, .tilt_x = 10.0F}) .ok() ); EXPECT_TRUE(pen_tablet.pen_tablet->button(lvh::PenButton::primary, true).ok()); EXPECT_TRUE(pen_tablet.pen_tablet->button(lvh::PenButton::primary, false).ok()); }