From b1b52c201f085afcf8c472a2ea3f2225311918e2 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 5 Oct 2021 12:12:40 -0700 Subject: [PATCH] [XCB] Add test fixtures for xcb connection and keyboard setup Signed-off-by: Chris Burel --- .../Platform/Common/Xcb/MockXcbInterface.cpp | 38 +++++- .../Platform/Common/Xcb/MockXcbInterface.h | 17 ++- .../Common/Xcb/XcbBaseTestFixture.cpp | 25 ++++ .../Platform/Common/Xcb/XcbBaseTestFixture.h | 29 +++++ .../Xcb/XcbInputDeviceKeyboardTests.cpp | 121 +++++++++++------- .../Xcb/azframework_xcb_tests_files.cmake | 2 + 6 files changed, 177 insertions(+), 55 deletions(-) create mode 100644 Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp create mode 100644 Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp index 64c3967fdc..4797f1133d 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp @@ -28,6 +28,10 @@ xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c) { return MockXcbInterface::Instance()->xcb_poll_for_event(c); } +xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t cookie) +{ + return MockXcbInterface::Instance()->xcb_request_check(c, cookie); +} // ---------------------------------------------------------------------------- // xcb-xkb @@ -39,6 +43,10 @@ xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c, { return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e); } +xcb_void_cookie_t xcb_xkb_select_events(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details) +{ + return MockXcbInterface::Instance()->xcb_xkb_select_events(c, deviceSpec, affectWhich, clear, selectAll, affectMap, map, details); +} // ---------------------------------------------------------------------------- // xkb-x11 @@ -46,7 +54,7 @@ int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t* connection) { return MockXcbInterface::Instance()->xkb_x11_get_core_keyboard_device_id(connection); } -struct xkb_keymap* xkb_x11_keymap_new_from_device(struct xkb_context* context, xcb_connection_t* connection, int32_t device_id, enum xkb_keymap_compile_flags flags) +xkb_keymap* xkb_x11_keymap_new_from_device(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags) { return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags); } @@ -54,28 +62,46 @@ xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* c { return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id); } +int xkb_x11_setup_xkb_extension( + xcb_connection_t* connection, + uint16_t major_xkb_version, + uint16_t minor_xkb_version, + xkb_x11_setup_xkb_extension_flags flags, + uint16_t* major_xkb_version_out, + uint16_t* minor_xkb_version_out, + uint8_t* base_event_out, + uint8_t* base_error_out) +{ + return MockXcbInterface::Instance()->xkb_x11_setup_xkb_extension( + connection, major_xkb_version, minor_xkb_version, flags, major_xkb_version_out, minor_xkb_version_out, base_event_out, + base_error_out); +} // ---------------------------------------------------------------------------- // xkbcommon -xkb_context* xkb_context_new(enum xkb_context_flags flags) +xkb_context* xkb_context_new(xkb_context_flags flags) { return MockXcbInterface::Instance()->xkb_context_new(flags); } -void xkb_context_unref(xkb_context *context) +void xkb_context_unref(xkb_context* context) { return MockXcbInterface::Instance()->xkb_context_unref(context); } -void xkb_keymap_unref(xkb_keymap *keymap) +void xkb_keymap_unref(xkb_keymap* keymap) { return MockXcbInterface::Instance()->xkb_keymap_unref(keymap); } -void xkb_state_unref(xkb_state *state) +void xkb_state_unref(xkb_state* state) { return MockXcbInterface::Instance()->xkb_state_unref(state); } -xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key) +xkb_keysym_t xkb_state_key_get_one_sym(xkb_state* state, xkb_keycode_t key) { return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key); } +int xkb_state_key_get_utf8(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size) +{ + return MockXcbInterface::Instance()->xkb_state_key_get_utf8(state, key, buffer, size); +} } diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h index ecda005830..4adb73caeb 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h @@ -17,6 +17,7 @@ #include #undef explicit #include +#include #include "Printers.h" @@ -35,6 +36,14 @@ struct xkb_keymap struct xkb_state { + enum class Modifier + { + None = 0, + Shift = 1, + Ctrl = 1 << 1, + Alt = 1 << 2, + }; + Modifier m_modifiers; }; class MockXcbInterface @@ -48,7 +57,7 @@ public: MockXcbInterface(MockXcbInterface&&) = delete; MockXcbInterface& operator=(const MockXcbInterface&) = delete; MockXcbInterface& operator=(MockXcbInterface&&) = delete; - ~MockXcbInterface() + virtual ~MockXcbInterface() { self = nullptr; } @@ -59,22 +68,26 @@ public: MOCK_CONST_METHOD2(xcb_connect, xcb_connection_t*(const char* displayname, int* screenp)); MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c)); MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c)); + MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie)); // xcb-xkb MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)); MOCK_CONST_METHOD3(xcb_xkb_use_extension_reply, xcb_xkb_use_extension_reply_t*(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD8(xcb_xkb_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details)); // xkb-x11 MOCK_CONST_METHOD1(xkb_x11_get_core_keyboard_device_id, int32_t(xcb_connection_t* connection)); MOCK_CONST_METHOD4(xkb_x11_keymap_new_from_device, xkb_keymap*(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags)); MOCK_CONST_METHOD3(xkb_x11_state_new_from_device, xkb_state*(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id)); + MOCK_CONST_METHOD8(xkb_x11_setup_xkb_extension, int(xcb_connection_t* connection, uint16_t major_xkb_version, uint16_t minor_xkb_version, xkb_x11_setup_xkb_extension_flags flags, uint16_t* major_xkb_version_out, uint16_t* minor_xkb_version_out, uint8_t* base_event_out, uint8_t* base_error_out)); // xkbcommon MOCK_CONST_METHOD1(xkb_context_new, xkb_context*(xkb_context_flags flags)); MOCK_CONST_METHOD1(xkb_context_unref, void(xkb_context* context)); MOCK_CONST_METHOD1(xkb_keymap_unref, void(xkb_keymap* keymap)); MOCK_CONST_METHOD1(xkb_state_unref, void(xkb_state* state)); - MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state *state, xkb_keycode_t key)); + MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state* state, xkb_keycode_t key)); + MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(struct xkb_state* state, xkb_keycode_t key, char* buffer, size_t size)); private: static inline MockXcbInterface* self = nullptr; diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp new file mode 100644 index 0000000000..937cb04e14 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "XcbBaseTestFixture.h" + +namespace AzFramework +{ + void XcbBaseTestFixture::SetUp() + { + using testing::Return; + using testing::_; + + testing::Test::SetUp(); + + EXPECT_CALL(m_interface, xcb_connect(_, _)) + .WillOnce(Return(&m_connection)); + EXPECT_CALL(m_interface, xcb_disconnect(&m_connection)) + .Times(1); + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h new file mode 100644 index 0000000000..8e9b008fc1 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include "MockXcbInterface.h" + +namespace AzFramework +{ + // Sets up mock behavior for the xcb library, providing an xcb_connection_t that is returned from a call to xcb_connect + class XcbBaseTestFixture + : public testing::Test + { + public: + void SetUp() override; + + protected: + testing::NiceMock m_interface; + xcb_connection_t m_connection{}; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp index 1b494c9525..9eb93fb4ae 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include @@ -15,6 +16,7 @@ #include #include "MockXcbInterface.h" #include "Actions.h" +#include "XcbBaseTestFixture.h" template xcb_generic_event_t MakeEvent(T event) @@ -24,26 +26,74 @@ xcb_generic_event_t MakeEvent(T event) namespace AzFramework { - TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents) + // Sets up default behavior for mock keyboard responses to xcb methods + class XcbInputDeviceKeyboardTests + : public XcbBaseTestFixture { - using testing::Return; + void SetUp() override + { + using testing::Return; + using testing::SetArgPointee; + using testing::_; + + XcbBaseTestFixture::SetUp(); + EXPECT_CALL(m_interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS)) + .WillOnce(Return(&m_xkbContext)); + EXPECT_CALL(m_interface, xkb_context_unref(&m_xkbContext)) + .Times(1); + + EXPECT_CALL(m_interface, xkb_x11_keymap_new_from_device(&m_xkbContext, &m_connection, s_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)) + .WillOnce(Return(&m_xkbKeymap)); + EXPECT_CALL(m_interface, xkb_keymap_unref(&m_xkbKeymap)) + .Times(1); + + EXPECT_CALL(m_interface, xkb_x11_state_new_from_device(&m_xkbKeymap, &m_connection, s_coreDeviceId)) + .WillOnce(Return(&m_xkbState)); + EXPECT_CALL(m_interface, xkb_state_unref(&m_xkbState)) + .Times(1); + + ON_CALL(m_interface, xkb_x11_setup_xkb_extension(&m_connection, 1, 0, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, _, _, _, _)) + .WillByDefault(DoAll( + SetArgPointee<6>(s_xkbEventCode), // Set the "base_event_out" argument to the xkbEventCode, the value to identify XKB events + Return(1) + )); + + constexpr unsigned int xcbXkbSelectEventsSequence = 1; + ON_CALL(m_interface, xcb_xkb_select_events(&m_connection, _, _, _, _, _, _, _)) + .WillByDefault(Return(xcb_void_cookie_t{/*.sequence = */ xcbXkbSelectEventsSequence})); + ON_CALL(m_interface, xcb_request_check(&m_connection, testing::Field(&xcb_void_cookie_t::sequence, testing::Eq(xcbXkbSelectEventsSequence)))) + .WillByDefault(Return(nullptr)); // indicates success + + ON_CALL(m_interface, xkb_x11_get_core_keyboard_device_id(&m_connection)) + .WillByDefault(Return(s_coreDeviceId)); + + ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey)) + .WillByDefault(Return(XKB_KEY_a)) + ; + } + + protected: + xkb_context m_xkbContext{}; + xkb_keymap m_xkbKeymap{}; + xkb_state m_xkbState{}; + static constexpr int32_t s_coreDeviceId{1}; + static constexpr uint8_t s_xkbEventCode{85}; + static constexpr xcb_keycode_t s_keycodeForAKey{38}; + }; + + TEST_F(XcbInputDeviceKeyboardTests, InputChannelsUpdateStateFromXcbEvents) + { + using testing::DoAll; using testing::Eq; + using testing::Return; + using testing::SetArgPointee; using testing::_; - MockXcbInterface interface; - - xcb_connection_t connection{}; - xkb_context xkbContext{}; - xkb_keymap xkbKeymap{}; - xkb_state xkbState{}; - const int32_t coreDeviceId{1}; - - constexpr xcb_keycode_t keycodeForAKey = 38; const AZStd::array events { MakeEvent(xcb_key_press_event_t{ /*.response_type = */ XCB_KEY_PRESS, - /*.detail = */ keycodeForAKey, + /*.detail = */ s_keycodeForAKey, /*.sequence = */ 0, /*.time = */ 0, /*.root = */ 0, @@ -59,7 +109,7 @@ namespace AzFramework }), MakeEvent(xcb_key_release_event_t{ /*.response_type = */ XCB_KEY_RELEASE, - /*.detail = */ keycodeForAKey, + /*.detail = */ s_keycodeForAKey, /*.sequence = */ 0, /*.time = */ 0, /*.root = */ 0, @@ -75,51 +125,28 @@ namespace AzFramework }), }; - EXPECT_CALL(interface, xcb_connect(_, _)) - .WillOnce(Return(&connection)); - EXPECT_CALL(interface, xcb_disconnect(&connection)) - .Times(1); - - EXPECT_CALL(interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS)) - .WillOnce(Return(&xkbContext)); - EXPECT_CALL(interface, xkb_context_unref(&xkbContext)) - .Times(1); - - EXPECT_CALL(interface, xkb_x11_keymap_new_from_device(&xkbContext, &connection, coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)) - .WillOnce(Return(&xkbKeymap)); - EXPECT_CALL(interface, xkb_keymap_unref(&xkbKeymap)) - .Times(1); - - EXPECT_CALL(interface, xkb_x11_state_new_from_device(&xkbKeymap, &connection, coreDeviceId)) - .WillOnce(Return(&xkbState)); - EXPECT_CALL(interface, xkb_state_unref(&xkbState)) - .Times(1); - - EXPECT_CALL(interface, xcb_xkb_use_extension(&connection, 1, 0)); - EXPECT_CALL(interface, xcb_xkb_use_extension_reply(&connection, _, _)) - .WillOnce(ReturnMalloc( - /* .response_type =*/static_cast(XCB_XKB_USE_EXTENSION), - /* .supported =*/ static_cast(1)) - ); - EXPECT_CALL(interface, xkb_x11_get_core_keyboard_device_id(&connection)) - .WillRepeatedly(Return(coreDeviceId)); - // Set the expectations for the events that will be generated // nullptr entries represent when the event queue is empty, and will cause // PumpSystemEventLoopUntilEmpty to return // event pointers are freed by the calling code, so we malloc new copies // here - EXPECT_CALL(interface, xcb_poll_for_event(&connection)) + EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection)) .WillOnce(ReturnMalloc(events[0])) .WillOnce(Return(nullptr)) .WillOnce(ReturnMalloc(events[1])) .WillOnce(Return(nullptr)) ; - EXPECT_CALL(interface, xkb_state_key_get_one_sym(&xkbState, keycodeForAKey)) - .WillOnce(Return(XKB_KEY_a)) - .WillOnce(Return(XKB_KEY_a)) - ; + EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey)) + .Times(2); + + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0)) + .WillRepeatedly(Return(1)); + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, _, 2)) + .WillRepeatedly(DoAll( + SetArgPointee<2>('a'), + Return(1) + )); Application application; application.Start({}, {}); diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake index aebd28222b..0ba1ece9c7 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake @@ -13,5 +13,7 @@ set(FILES MockXcbInterface.h Printers.cpp Printers.h + XcbBaseTestFixture.cpp + XcbBaseTestFixture.h XcbInputDeviceKeyboardTests.cpp )