From 881b415d6025b5fc91e159e7d519ca4a31a24a16 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Thu, 19 May 2022 11:18:45 +0800 Subject: [PATCH] Implement joystick tests Implement tests for joystick axis, hat, and button events. --- CMakeLists.txt | 9 +- __mocks__/SDL_events.mock.h | 6 + __mocks__/SDL_joystick.mock.h | 21 ++ src/packages/game/input/IZ_joystick.c | 28 +- src/packages/game/input/IZ_joystick.h | 7 + src/packages/game/input/IZ_keyboard.c | 5 +- src/packages/game/input/IZ_keyboard.test.c | 101 ----- src/packages/game/input/input.test.c | 417 +++++++++++++++++++++ 8 files changed, 482 insertions(+), 112 deletions(-) create mode 100644 __mocks__/SDL_events.mock.h create mode 100644 __mocks__/SDL_joystick.mock.h delete mode 100644 src/packages/game/input/IZ_keyboard.test.c create mode 100644 src/packages/game/input/input.test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 09f5b06..8c03fbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,6 @@ add_executable( src/packages/test/IZ_test.h __mocks__/minIni.mock.h - __mocks__/SDL_keyboard.mock.h src/packages/game/IZ_config.h src/packages/game/__mocks__/IZ_config.mock.h @@ -74,17 +73,19 @@ add_executable( __mocks__/minIni.mock.h __mocks__/SDL_keyboard.mock.h + __mocks__/SDL_events.mock.h + __mocks__/SDL_joystick.mock.h src/packages/game/IZ_config.h src/packages/game/__mocks__/IZ_config.mock.h src/packages/game/input/IZ_keyboard.h src/packages/game/input/IZ_keyboard.c - src/packages/game/input/IZ_keyboard.test.c -# src/packages/game/input/IZ_joystick.h -# src/packages/game/input/IZ_joystick.c + src/packages/game/input/IZ_joystick.h + src/packages/game/input/IZ_joystick.c + src/packages/game/input/input.test.c ) if (WIN32) diff --git a/__mocks__/SDL_events.mock.h b/__mocks__/SDL_events.mock.h new file mode 100644 index 0000000..f920ade --- /dev/null +++ b/__mocks__/SDL_events.mock.h @@ -0,0 +1,6 @@ +#ifndef SDL_EVENTS_MOCK_H +#define SDL_EVENTS_MOCK_H + +#include "SDL_keyboard.mock.h" + +#endif diff --git a/__mocks__/SDL_joystick.mock.h b/__mocks__/SDL_joystick.mock.h new file mode 100644 index 0000000..a1ba807 --- /dev/null +++ b/__mocks__/SDL_joystick.mock.h @@ -0,0 +1,21 @@ +#ifndef SDL_JOYSTICK_MOCK_H +#define SDL_JOYSTICK_MOCK_H + +#include "../src/packages/test/IZ_test.h" + +typedef struct _SDL_Joystick {} SDL_Joystick; + +mock(SDL_JoystickOpen) SDL_Joystick* SDL_JoystickOpen(int device_index) { + static SDL_Joystick joystick; + mock_return(SDL_JoystickOpen) &joystick; +} + +mock(SDL_NumJoysticks) int SDL_NumJoysticks(void) { + mock_return(SDL_NumJoysticks) 1; +} + +mock(SDL_JoystickInstanceID) int SDL_JoystickInstanceID(SDL_Joystick* joystick) { + mock_return(SDL_JoystickInstanceID) 0; +} + +#endif diff --git a/src/packages/game/input/IZ_joystick.c b/src/packages/game/input/IZ_joystick.c index 5c252b2..f2a0149 100644 --- a/src/packages/game/input/IZ_joystick.c +++ b/src/packages/game/input/IZ_joystick.c @@ -19,21 +19,32 @@ void IZ_HandleJoystickDeviceEvents(SDL_Event e, IZ_JoystickState* state) { void IZ_HandleJoystickAxisEvents(SDL_Event e, IZ_JoystickState* state, IZ_Action* action) { if (e.type == SDL_JOYAXISMOTION) { - if (e.jaxis.axis == 0 || e.jaxis.axis == 3) { + if ( + e.jaxis.axis == IZ_JOYAXIS_DIRECTION_HORIZONTAL1 + || e.jaxis.axis == IZ_JOYAXIS_DIRECTION_HORIZONTAL2 + ) { *action &= ~(0x1 << IZ_ACTION_INDEX_RIGHT); *action &= ~(0x1 << IZ_ACTION_INDEX_LEFT); if (e.jaxis.value > state->config.axis_threshold) { *action |= (0x1 << IZ_ACTION_INDEX_RIGHT); - } else if (e.jaxis.value <= -state->config.axis_threshold) { + return; + } + if (e.jaxis.value <= -state->config.axis_threshold) { *action |= (0x1 << IZ_ACTION_INDEX_LEFT); } + return; } - if (e.jaxis.axis == 1 || e.jaxis.axis == 4) { + if ( + e.jaxis.axis == IZ_JOYAXIS_DIRECTION_VERTICAL1 + || e.jaxis.axis == IZ_JOYAXIS_DIRECTION_VERTICAL2 + ) { *action &= ~(0x1 << IZ_ACTION_INDEX_UP); *action &= ~(0x1 << IZ_ACTION_INDEX_DOWN); - if (e.jaxis.value > -state->config.axis_threshold) { + if (e.jaxis.value > state->config.axis_threshold) { *action |= (0x1 << IZ_ACTION_INDEX_DOWN); - } else if (e.jaxis.value <= -state->config.axis_threshold) { + return; + } + if (e.jaxis.value <= -state->config.axis_threshold) { *action |= (0x1 << IZ_ACTION_INDEX_UP); } } @@ -56,10 +67,15 @@ void IZ_HandleJoystickButtonEvents(SDL_Event e, IZ_JoystickState* state, IZ_Acti for (uint8_t i = 4; i < CONTROLS; i += 1) { if (e.jbutton.button == state->config.control_mapping[i]) { const uint16_t bitflag = (0x1 << i); + if (e.type == SDL_JOYBUTTONDOWN) { *action |= bitflag; - } else if (e.type == SDL_JOYBUTTONUP) { + break; + } + + if (e.type == SDL_JOYBUTTONUP) { *action &= ~bitflag; + break; } } } diff --git a/src/packages/game/input/IZ_joystick.h b/src/packages/game/input/IZ_joystick.h index ce43f35..8342fb0 100644 --- a/src/packages/game/input/IZ_joystick.h +++ b/src/packages/game/input/IZ_joystick.h @@ -9,6 +9,13 @@ typedef uint8_t IZ_PadButton; +typedef enum { + IZ_JOYAXIS_DIRECTION_HORIZONTAL1 = 0, + IZ_JOYAXIS_DIRECTION_VERTICAL1 = 1, + IZ_JOYAXIS_DIRECTION_HORIZONTAL2 = 3, + IZ_JOYAXIS_DIRECTION_VERTICAL2 = 4, +} IZ_JoyAxisDirection; + typedef struct { uint16_t axis_threshold; IZ_PadButton control_mapping[CONTROLS]; diff --git a/src/packages/game/input/IZ_keyboard.c b/src/packages/game/input/IZ_keyboard.c index 1d54edd..c089d60 100644 --- a/src/packages/game/input/IZ_keyboard.c +++ b/src/packages/game/input/IZ_keyboard.c @@ -6,8 +6,11 @@ void IZ_HandleKeyboardEvents(SDL_Event e, IZ_KeyboardState* state, IZ_Action* ac const uint16_t bitflag = (0x1 << i); if (e.type == SDL_KEYDOWN) { *action |= bitflag; - } else if (e.type == SDL_KEYUP) { + break; + } + if (e.type == SDL_KEYUP) { *action &= ~bitflag; + break; } } } diff --git a/src/packages/game/input/IZ_keyboard.test.c b/src/packages/game/input/IZ_keyboard.test.c deleted file mode 100644 index 1c24c6b..0000000 --- a/src/packages/game/input/IZ_keyboard.test.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "../../../__mocks__/SDL_keyboard.mock.h" -#include "../../../__mocks__/minIni.mock.h" -#include "../__mocks__/IZ_config.mock.h" -#include "IZ_keyboard.h" - -spec("input/keyboard") { - describe("HandleKeyboardEvents") { - static SDL_Event e; - static IZ_KeyboardState state; - static IZ_Action action; - - for (uint8_t i = 0; i < CONTROLS; i += 1) { - it("handles %s action activation", ACTION_NAMES[i]) { - e.type = SDL_KEYDOWN; - e.key.keysym.sym = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; - state.config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; - action = 0; - - IZ_HandleKeyboardEvents(e, &state, &action); - check( - action == (0x1 << i), - "Action not set." - ); - } - - it("handles %s action deactivation", ACTION_NAMES[i]) { - e.type = SDL_KEYUP; - e.key.keysym.sym = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; - state.config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; - action = ~0; - - IZ_HandleKeyboardEvents(e, &state, &action); - check( - !(action & (0x1 << i)), - "Action not unset." - ); - } - } - } - - describe("LoadKeyboardConfig") { - static IZ_KeyboardConfig config; - - after_each() { - mock_reset(IZ_GetConfigPath); - } - - after_each() { - mock_reset(ini_gets); - } - - it("calls load method") { - mock_set_expected_calls(ini_gets, CONTROLS); - - IZ_LoadKeyboardConfig(&config, 0); - - check( - mock_is_called(IZ_GetConfigPath), - "SDL_GetBasePath() not called." - ); - - check( - mock_get_expected_calls(ini_gets) == mock_get_actual_calls(ini_gets), - "Call count mismatch for ini_gets() (expected %u, received %u).", - mock_get_expected_calls(ini_gets), - mock_get_actual_calls(ini_gets) - ); - } - } - - describe("SaveKeyboardConfig") { - static IZ_KeyboardConfig config; - - after_each() { - mock_reset(IZ_GetConfigPath); - } - - after_each() { - mock_reset(ini_puts); - } - - before_each() { - for (uint8_t i = 0; i < CONTROLS; i += 1) { - config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; - } - } - - it("calls save method") { - mock_set_expected_calls(ini_puts, CONTROLS); - - IZ_SaveKeyboardConfig(&config, 0); - - check( - mock_get_expected_calls(ini_puts) == mock_get_actual_calls(ini_puts), - "Call count mismatch for ini_puts() (expected %u, received %u).", - mock_get_expected_calls(ini_puts), - mock_get_actual_calls(ini_puts) - ); - } - } -} diff --git a/src/packages/game/input/input.test.c b/src/packages/game/input/input.test.c new file mode 100644 index 0000000..e494802 --- /dev/null +++ b/src/packages/game/input/input.test.c @@ -0,0 +1,417 @@ +#include "../../../__mocks__/SDL_keyboard.mock.h" +#include "../../../__mocks__/SDL_joystick.mock.h" +#include "../../../__mocks__/minIni.mock.h" +#include "../__mocks__/IZ_config.mock.h" +#include "IZ_keyboard.h" +#include "IZ_joystick.h" + +short int GenerateAxisValueWithinThreshold(short int threshold) { + return rand() % threshold; +} + +short int GenerateAxisValueOutsideThreshold(short int threshold) { + return threshold + (rand() % (RAND_MAX - threshold - 1)) + 1; +} + +spec("input") { + describe("joystick") { + describe("HandleJoystickEvents") { + static SDL_Event e; + static IZ_JoystickState state; + static IZ_Action action; + + describe("on axis motion events") { + before_each() { + e.type = SDL_JOYAXISMOTION; + state.config.axis_threshold = 8000; + } + + describe("on primary horizontal direction") { + before_each() { + e.jaxis.axis = IZ_JOYAXIS_DIRECTION_HORIZONTAL1; + } + + it("handles positive motion") { + e.jaxis.value = GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_RIGHT), + "Action not set." + ); + } + + it("handles negative motion") { + e.jaxis.value = -GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_LEFT), + "Action not set." + ); + } + + it("handles neutral motion") { + e.jaxis.value = GenerateAxisValueWithinThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == 0, + "Action not set." + ); + } + } + + describe("on secondary horizontal direction") { + before_each() { + e.jaxis.axis = IZ_JOYAXIS_DIRECTION_HORIZONTAL2; + } + + it("handles positive motion") { + e.jaxis.value = GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_RIGHT), + "Action not set." + ); + } + + it("handles negative motion") { + e.jaxis.value = -GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_LEFT), + "Action not set." + ); + } + + it("handles neutral motion") { + e.jaxis.value = GenerateAxisValueWithinThreshold(state.config.axis_threshold);; + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == 0, + "Action not set." + ); + } + } + + describe("on primary vertical direction") { + before_each() { + e.jaxis.axis = IZ_JOYAXIS_DIRECTION_VERTICAL1; + } + + it("handles positive motion") { + e.jaxis.value = GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_DOWN), + "Action not set." + ); + } + + it("handles negative motion") { + e.jaxis.value = -GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_UP), + "Action not set." + ); + } + + it("handles neutral motion") { + e.jaxis.value = GenerateAxisValueWithinThreshold(state.config.axis_threshold);; + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == 0, + "Action not set." + ); + } + } + + describe("on secondary vertical direction") { + before_each() { + e.jaxis.axis = IZ_JOYAXIS_DIRECTION_VERTICAL2; + } + + it("handles positive motion") { + e.jaxis.value = GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_DOWN), + "Action not set." + ); + } + + it("handles negative motion") { + e.jaxis.value = -GenerateAxisValueOutsideThreshold(state.config.axis_threshold); + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1 << IZ_ACTION_INDEX_UP), + "Action not set." + ); + } + + it("handles neutral motion") { + e.jaxis.value = GenerateAxisValueWithinThreshold(state.config.axis_threshold);; + action = 0; + + printf("(axis value: %d) ", e.jaxis.value); + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == 0, + "Action not set." + ); + } + } + } + + describe("on hat motion events") { + before_each() { + e.type = SDL_JOYHATMOTION; + } + + for (uint8_t i = 0; i < 4; i += 1) { + it("handles motion for %s action", ACTION_NAMES[i]) { + e.jhat.value = (0x1u << i); + action = 0; + + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1u << i), + "Action not set." + ); + } + + it("handles motion for %s deactivation", ACTION_NAMES[i]) { + e.jhat.value = 0; + action = ~0; + + IZ_HandleJoystickEvents(e, &state, &action); + check( + !(action & (0x1 << i)), + "Action not unset." + ); + } + } + } + + describe("on button events") { + for (uint8_t i = 4; i < CONTROLS; i += 1) { + it("handles %s action activation", ACTION_NAMES[i]) { + e.type = SDL_JOYBUTTONDOWN; + e.jbutton.button = IZ_DEFAULT_JOYSTICK_CONTROLS[0][i]; + state.config.control_mapping[i] = IZ_DEFAULT_JOYSTICK_CONTROLS[0][i]; + action = 0; + + IZ_HandleJoystickEvents(e, &state, &action); + check( + action == (0x1u << i), + "Action not set." + ); + } + + it("handles %s action deactivation", ACTION_NAMES[i]) { + e.type = SDL_JOYBUTTONUP; + e.jbutton.button = IZ_DEFAULT_JOYSTICK_CONTROLS[0][i]; + state.config.control_mapping[i] = IZ_DEFAULT_JOYSTICK_CONTROLS[0][i]; + action = ~0; + + IZ_HandleJoystickEvents(e, &state, &action); + check( + !(action & (0x1 << i)), + "Action not unset." + ); + } + } + } + } + + describe("LoadJoystickConfig") { + static IZ_JoystickConfig config; + + after_each() { + mock_reset(IZ_GetConfigPath); + } + + after_each() { + mock_reset(ini_getl); + } + + it("calls load method") { + mock_set_expected_calls(ini_getl, CONTROLS - 4 + 1); + + IZ_LoadJoystickConfig(&config, 0); + + check( + mock_is_called(IZ_GetConfigPath), + "SDL_GetBasePath() not called." + ); + + check( + mock_get_expected_calls(ini_getl) == mock_get_actual_calls(ini_getl), + "Call count mismatch for ini_getl() (expected %u, received %u).", + mock_get_expected_calls(ini_getl), + mock_get_actual_calls(ini_getl) + ); + } + } + + describe("SaveJoystickConfig") { + static IZ_JoystickConfig config; + + after_each() { + mock_reset(IZ_GetConfigPath); + } + + after_each() { + mock_reset(ini_putl); + } + + before_each() { + for (uint8_t i = 0; i < CONTROLS; i += 1) { + config.control_mapping[i] = IZ_DEFAULT_JOYSTICK_CONTROLS[0][i]; + } + } + + it("calls save method") { + mock_set_expected_calls(ini_putl, CONTROLS - 4 + 1); + + IZ_SaveJoystickConfig(&config, 0); + + check( + mock_get_expected_calls(ini_putl) == mock_get_actual_calls(ini_putl), + "Call count mismatch for ini_putl() (expected %u, received %u).", + mock_get_expected_calls(ini_putl), + mock_get_actual_calls(ini_putl) + ); + } + } + } + + describe("keyboard") { + describe("HandleKeyboardEvents") { + static SDL_Event e; + static IZ_KeyboardState state; + static IZ_Action action; + + for (uint8_t i = 0; i < CONTROLS; i += 1) { + it("handles %s action activation", ACTION_NAMES[i]) { + e.type = SDL_KEYDOWN; + e.key.keysym.sym = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; + state.config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; + action = 0; + + IZ_HandleKeyboardEvents(e, &state, &action); + check( + action == (0x1 << i), + "Action not set." + ); + } + + it("handles %s action deactivation", ACTION_NAMES[i]) { + e.type = SDL_KEYUP; + e.key.keysym.sym = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; + state.config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; + action = ~0; + + IZ_HandleKeyboardEvents(e, &state, &action); + check( + !(action & (0x1 << i)), + "Action not unset." + ); + } + } + } + + describe("LoadKeyboardConfig") { + static IZ_KeyboardConfig config; + + after_each() { + mock_reset(IZ_GetConfigPath); + } + + after_each() { + mock_reset(ini_gets); + } + + it("calls load method") { + mock_set_expected_calls(ini_gets, CONTROLS); + + IZ_LoadKeyboardConfig(&config, 0); + + check( + mock_is_called(IZ_GetConfigPath), + "SDL_GetBasePath() not called." + ); + + check( + mock_get_expected_calls(ini_gets) == mock_get_actual_calls(ini_gets), + "Call count mismatch for ini_gets() (expected %u, received %u).", + mock_get_expected_calls(ini_gets), + mock_get_actual_calls(ini_gets) + ); + } + } + + describe("SaveKeyboardConfig") { + static IZ_KeyboardConfig config; + + after_each() { + mock_reset(IZ_GetConfigPath); + } + + after_each() { + mock_reset(ini_puts); + } + + before_each() { + for (uint8_t i = 0; i < CONTROLS; i += 1) { + config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_CONTROLS[0][i]; + } + } + + it("calls save method") { + mock_set_expected_calls(ini_puts, CONTROLS); + + IZ_SaveKeyboardConfig(&config, 0); + + check( + mock_get_expected_calls(ini_puts) == mock_get_actual_calls(ini_puts), + "Call count mismatch for ini_puts() (expected %u, received %u).", + mock_get_expected_calls(ini_puts), + mock_get_actual_calls(ini_puts) + ); + } + } + } +}