Add a unit test for XcbInputDeviceKeyboard (#4393)

* Use XcbUniquePtr to manage xcb resources

Signed-off-by: Chris Burel <burelc@amazon.com>

* Add a unit test for XcbInputDeviceKeyboard

Signed-off-by: Chris Burel <burelc@amazon.com>
This commit is contained in:
Chris Burel
2021-09-30 11:19:23 -07:00
committed by GitHub
parent 328cde0516
commit aa8594b491
17 changed files with 492 additions and 11 deletions
@@ -76,6 +76,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
NAMESPACE AZ
FILES_CMAKE
Tests/frameworktests_files.cmake
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
@@ -93,6 +94,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
NAME AZ::AzFramework.Tests
)
include(${pal_dir}/platform_specific_test_targets.cmake)
endif()
endif()
@@ -8,6 +8,7 @@
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
namespace AzFramework
{
@@ -17,8 +18,8 @@ namespace AzFramework
{
public:
XcbConnectionManagerImpl()
: m_xcbConnection(xcb_connect(nullptr, nullptr))
{
m_xcbConnection = xcb_connect(nullptr, nullptr);
AZ_Error("Application", m_xcbConnection != nullptr, "Unable to connect to X11 Server.");
XcbConnectionManagerBus::Handler::BusConnect();
}
@@ -26,16 +27,15 @@ namespace AzFramework
~XcbConnectionManagerImpl() override
{
XcbConnectionManagerBus::Handler::BusDisconnect();
xcb_disconnect(m_xcbConnection);
}
xcb_connection_t* GetXcbConnection() const override
{
return m_xcbConnection;
return m_xcbConnection.get();
}
private:
xcb_connection_t* m_xcbConnection = nullptr;
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -65,10 +65,9 @@ namespace AzFramework
{
if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection())
{
if (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection))
if (auto event = XcbStdFreePtr<xcb_generic_event_t>{xcb_poll_for_event(xcbConnection)})
{
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event);
free(event);
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event.get());
}
}
}
@@ -78,10 +77,9 @@ namespace AzFramework
{
if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection())
{
while (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection))
while (auto event = XcbStdFreePtr<xcb_generic_event_t>{xcb_poll_for_event(xcbConnection)})
{
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event);
free(event);
XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event.get());
}
}
}
@@ -0,0 +1,9 @@
/*
* 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 <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,7 @@
#
# 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
#
#
@@ -0,0 +1,27 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_2_VALUE_PARAMS(p0, p1)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1 };
return value;
}
@@ -0,0 +1,9 @@
/*
* 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 <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,81 @@
/*
* 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 "MockXcbInterface.h"
// The functions defined in this file will take precedence over those defined
// in the real libxcb.so, allowing the test code to use a mock implementation
extern "C"
{
// ----------------------------------------------------------------------------
// xcb
xcb_connection_t* xcb_connect(const char* displayname, int* screenp)
{
return MockXcbInterface::Instance()->xcb_connect(displayname, screenp);
}
void xcb_disconnect(xcb_connection_t* c)
{
return MockXcbInterface::Instance()->xcb_disconnect(c);
}
xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c)
{
return MockXcbInterface::Instance()->xcb_poll_for_event(c);
}
// ----------------------------------------------------------------------------
// xcb-xkb
xcb_xkb_use_extension_cookie_t xcb_xkb_use_extension(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)
{
return MockXcbInterface::Instance()->xcb_xkb_use_extension(c, wantedMajor, wantedMinor);
}
xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e);
}
// ----------------------------------------------------------------------------
// xkb-x11
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)
{
return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags);
}
xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id)
{
return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id);
}
// ----------------------------------------------------------------------------
// xkbcommon
xkb_context* xkb_context_new(enum xkb_context_flags flags)
{
return MockXcbInterface::Instance()->xkb_context_new(flags);
}
void xkb_context_unref(xkb_context *context)
{
return MockXcbInterface::Instance()->xkb_context_unref(context);
}
void xkb_keymap_unref(xkb_keymap *keymap)
{
return MockXcbInterface::Instance()->xkb_keymap_unref(keymap);
}
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)
{
return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key);
}
}
@@ -0,0 +1,81 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#define explicit ExplicitIsACXXKeyword
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon.h>
#include "Printers.h"
// xcb / xkb do not provide definitions of these structs, but the tests need some concrete value for them.
struct xcb_connection_t
{
};
struct xkb_context
{
};
struct xkb_keymap
{
};
struct xkb_state
{
};
class MockXcbInterface
{
public:
MockXcbInterface()
{
self = this;
}
MockXcbInterface(const MockXcbInterface&) = delete;
MockXcbInterface(MockXcbInterface&&) = delete;
MockXcbInterface& operator=(const MockXcbInterface&) = delete;
MockXcbInterface& operator=(MockXcbInterface&&) = delete;
~MockXcbInterface()
{
self = nullptr;
}
static MockXcbInterface* Instance() { return self; }
// xcb
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));
// 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));
// 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));
// 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));
private:
static inline MockXcbInterface* self = nullptr;
};
@@ -0,0 +1,33 @@
/*
* 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 "Printers.h"
#include <ostream>
#include <AzFramework/Input/Channels/InputChannel.h>
namespace AzFramework
{
void PrintTo(const InputChannel::State& state, std::ostream* os)
{
switch(state)
{
case InputChannel::State::Began:
*os << "Began";
break;
case InputChannel::State::Ended:
*os << "Ended";
break;
case InputChannel::State::Idle:
*os << "Idle";
break;
case InputChannel::State::Updated:
*os << "Updated";
break;
}
}
} // namespace AzFramework
@@ -0,0 +1,17 @@
/*
* 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 <iosfwd>
#include <AzFramework/Input/Channels/InputChannel.h>
namespace AzFramework
{
void PrintTo(const InputChannel::State& state, std::ostream* os);
} // namespace AzFramework
@@ -0,0 +1,145 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbInputDeviceKeyboard.h>
#include "MockXcbInterface.h"
#include "Actions.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
namespace AzFramework
{
TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents)
{
using testing::Return;
using testing::Eq;
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,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_key_release_event_t{
/*.response_type = */ XCB_KEY_RELEASE,
/*.detail = */ keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
};
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<xcb_xkb_use_extension_reply_t>(
/* .response_type =*/static_cast<uint8_t>(XCB_XKB_USE_EXTENSION),
/* .supported =*/ static_cast<uint8_t>(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))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[0]))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(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))
;
Application application;
application.Start({}, {});
const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA);
ASSERT_TRUE(inputChannel);
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended));
application.Stop();
}
} // namespace AzFramework
@@ -0,0 +1,17 @@
#
# 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
#
#
set(FILES
Actions.h
Main.cpp
MockXcbInterface.cpp
MockXcbInterface.h
Printers.cpp
Printers.h
XcbInputDeviceKeyboardTests.cpp
)
@@ -0,0 +1,33 @@
#
# 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
#
#
if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
# This library defines local implementations of all the used xcb functions,
# in order to allow tests to run in the absence of a running X server.
# These have to be in a separate library, so that the normal AzFramework
# tests do not need to set up a mock Xcb interface.
ly_add_target(
NAME AzFramework.Xcb.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
${pal_dir}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzFramework
AZ::AzTest
AZ::AzTestShared
AZ::AzFrameworkTestShared
)
ly_add_googletest(
NAME AZ::AzFramework.Xcb.Tests
)
endif()
@@ -0,0 +1,7 @@
#
# 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
#
#
@@ -0,0 +1,7 @@
#
# 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
#
#
@@ -0,0 +1,7 @@
#
# 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
#
#
@@ -7,7 +7,7 @@
#
set(FILES
../../AzCore/Tests/Main.cpp
Main.cpp
Spawnable/SpawnableEntitiesInterfaceTests.cpp
Spawnable/SpawnableEntitiesManagerTests.cpp
ArchiveCompressionTests.cpp