From 52fb193b8315a7a2d243d9049d5702df99f260e8 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Fri, 10 Jun 2022 18:42:43 +0800 Subject: [PATCH] Cover all input methods on tests Add tests for initialization and teardown for all input methods. --- CMakeLists.txt | 4 + __mocks__/SDL_joystick.mock.h | 4 +- __mocks__/portmidi.mock.h | 69 ++++ src/packages/game/IZ_app.c | 5 +- src/packages/game/geometry/geometry.test.c | 2 +- src/packages/game/input/IZ_input.c | 12 +- src/packages/game/input/IZ_input.h | 2 +- src/packages/game/input/IZ_joystick.c | 3 + src/packages/game/input/IZ_midi.c | 28 +- src/packages/game/input/IZ_midi.h | 9 +- src/packages/game/input/input.test.c | 349 +++++++++++++++++++++ 11 files changed, 471 insertions(+), 16 deletions(-) create mode 100644 __mocks__/portmidi.mock.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c3984..6b2be4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ add_executable( __mocks__/SDL_events.mock.h __mocks__/SDL_joystick.mock.h __mocks__/SDL_stdinc.mock.h + __mocks__/portmidi.mock.h src/packages/game/IZ_config.h @@ -89,6 +90,9 @@ add_executable( src/packages/game/input/IZ_joystick.h src/packages/game/input/IZ_joystick.c + src/packages/game/input/IZ_midi.h + src/packages/game/input/IZ_midi.c + src/packages/game/input/input.test.c ) diff --git a/__mocks__/SDL_joystick.mock.h b/__mocks__/SDL_joystick.mock.h index e374ded..0608704 100644 --- a/__mocks__/SDL_joystick.mock.h +++ b/__mocks__/SDL_joystick.mock.h @@ -6,13 +6,15 @@ typedef struct _SDL_Joystick {} SDL_Joystick; +#define MOCK_OPEN_JOYSTICKS 1 + mock(SDL_JoystickOpen) SDL_Joystick* SDL_JoystickOpen(i32 device_index) { static SDL_Joystick joystick; mock_return(SDL_JoystickOpen) &joystick; } mock(SDL_NumJoysticks) i32 SDL_NumJoysticks(void) { - mock_return(SDL_NumJoysticks) 1; + mock_return(SDL_NumJoysticks) MOCK_OPEN_JOYSTICKS; } mock(SDL_JoystickInstanceID) i32 SDL_JoystickInstanceID(SDL_Joystick* joystick) { diff --git a/__mocks__/portmidi.mock.h b/__mocks__/portmidi.mock.h new file mode 100644 index 0000000..d33a015 --- /dev/null +++ b/__mocks__/portmidi.mock.h @@ -0,0 +1,69 @@ +#ifndef PORTMIDI_MOCK_H +#define PORTMIDI_MOCK_H + +#define PORTMIDI_INCLUDED + +#include "../src/packages/game/IZ_common.h" + +typedef i32 PmDeviceID; +typedef void PortMidiStream; +typedef i32 PmTimestamp; +typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); +typedef i32 PmMessage; +typedef struct { + PmMessage message; + PmTimestamp timestamp; +} PmEvent; +typedef struct { + int structVersion; /**< @brief this internal structure version */ + const char *interf; /**< @brief underlying MIDI API, e.g. + "MMSystem" or "DirectX" */ + char *name; /**< @brief device name, e.g. "USB MidiSport 1x1" */ + int input; /**< @brief true iff input is available */ + int output; /**< @brief true iff output is available */ + int opened; /**< @brief used by generic PortMidi for error checking */ + int is_virtual; /**< @brief true iff this is/was a virtual device */ +} PmDeviceInfo; + +#define PmStream PortMidiStream + +static PmDeviceInfo MOCK_DEVICE_INFO = { + .output = 1, + .input = 1, + .name = "Mock MIDI Device", + .interf = "MMSystem", + .is_virtual = 0, + .opened = 0, + .structVersion = 1, +}; + +mock(Pm_Initialize) i32 Pm_Initialize(void) { + mock_return(Pm_Initialize) 0; +} + +mock(Pm_GetDeviceInfo) const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id) { + mock_return(Pm_GetDeviceInfo) &MOCK_DEVICE_INFO; +} + +mock(Pm_OpenInput) i32 Pm_OpenInput( + PortMidiStream** stream, + PmDeviceID inputDevice, + void* inputDriverInfo, + i32 bufferSize, + PmTimeProcPtr time_proc, + void* time_info +) { + mock_return(Pm_OpenInput) 0; +} + +mock(Pm_Close) i32 Pm_Close(PortMidiStream* stream) { + mock_return(Pm_Close) 0; +} + +#define MOCK_OPEN_MIDI_DEVICES 1 + +mock(Pm_CountDevices) i32 Pm_CountDevices(void) { + mock_return(Pm_CountDevices) MOCK_OPEN_MIDI_DEVICES; +} + +#endif diff --git a/src/packages/game/IZ_app.c b/src/packages/game/IZ_app.c index 4d2810a..53704c6 100644 --- a/src/packages/game/IZ_app.c +++ b/src/packages/game/IZ_app.c @@ -19,7 +19,10 @@ IZ_ProcedureResult IZ_AppInitialize(IZ_App* app) { return 2; } - IZ_InputInitialize(config_path, &app->input_state); + if (IZ_InputInitialize(config_path, &app->input_state)) { + return 3; + } + IZ_PoolInitialize(&app->pool, POOL_MAX_SIZE); // TODO put into its timer module diff --git a/src/packages/game/geometry/geometry.test.c b/src/packages/game/geometry/geometry.test.c index f219cf9..287b830 100644 --- a/src/packages/game/geometry/geometry.test.c +++ b/src/packages/game/geometry/geometry.test.c @@ -84,7 +84,7 @@ spec("geometry") { .up = 69.f, }; - static IZ_GeoCoord s = 2.f; + static f32 s = 2.f; static IZ_Vector2D expected = { .right = 840.f, diff --git a/src/packages/game/input/IZ_input.c b/src/packages/game/input/IZ_input.c index d6ed80f..c23cb08 100644 --- a/src/packages/game/input/IZ_input.c +++ b/src/packages/game/input/IZ_input.c @@ -9,7 +9,7 @@ void IZ_InputHandlePortMIDIEvents(PmEvent e, IZ_InputState* state) { IZ_MIDIInputHandleEvents(e, &state->midi_input_state, &state->action); } -void IZ_InputInitialize(const char* config_path, IZ_InputState* state) { +IZ_ProcedureResult IZ_InputInitialize(const char* config_path, IZ_InputState* state) { *state = (IZ_InputState) { .action = {}, .joystick_state = {}, @@ -17,25 +17,29 @@ void IZ_InputInitialize(const char* config_path, IZ_InputState* state) { .keyboard_state = {}, }; + IZ_ProcedureResult result = 0; + IZ_ProcedureResult joystick_result = IZ_JoystickInitialize(config_path, &state->joystick_state); if (joystick_result) { - fprintf_s(stderr, "Error committing joystick config. Code: %u.\n", joystick_result); + result |= 1; } IZ_ProcedureResult keyboard_result = IZ_KeyboardInitialize(config_path, &state->keyboard_state); if (keyboard_result) { - fprintf_s(stderr, "Error committing keyboard config. Code: %u.\n", keyboard_result); + result |= 2; } IZ_ProcedureResult midi_input_result = IZ_MIDIInputInitialize(config_path, &state->midi_input_state); if (midi_input_result) { - fprintf_s(stderr, "Error committing MIDI input config. Code: %u.\n", midi_input_result); + result |= 4; } u8 player_index; for (player_index = 0; player_index < PLAYERS; player_index += 1) { state->action[player_index] = 0; } + + return result; } void IZ_InputTeardown(IZ_InputState* state) { diff --git a/src/packages/game/input/IZ_input.h b/src/packages/game/input/IZ_input.h index c9595ef..e74a7f6 100644 --- a/src/packages/game/input/IZ_input.h +++ b/src/packages/game/input/IZ_input.h @@ -17,7 +17,7 @@ void IZ_InputHandleSDLEvents(SDL_Event, IZ_InputState*); void IZ_InputHandlePortMIDIEvents(PmEvent, IZ_InputState*); -void IZ_InputInitialize(const char*, IZ_InputState*); +IZ_ProcedureResult IZ_InputInitialize(const char*, IZ_InputState*); void IZ_InputTeardown(IZ_InputState*); diff --git a/src/packages/game/input/IZ_joystick.c b/src/packages/game/input/IZ_joystick.c index 51003a7..c0c5c4f 100644 --- a/src/packages/game/input/IZ_joystick.c +++ b/src/packages/game/input/IZ_joystick.c @@ -172,6 +172,9 @@ IZ_ProcedureResult IZ_JoystickInitialize(const char* config_path, IZ_JoystickSta u8 joysticks_count = SDL_NumJoysticks(); u8 player_index; for (player_index = 0; player_index < joysticks_count; player_index += 1) { + if (player_index >= PLAYERS) { + break; + } (*state)[player_index].device = SDL_JoystickOpen(state[player_index]->config.device_id); } diff --git a/src/packages/game/input/IZ_midi.c b/src/packages/game/input/IZ_midi.c index 3a123ee..1193731 100644 --- a/src/packages/game/input/IZ_midi.c +++ b/src/packages/game/input/IZ_midi.c @@ -18,15 +18,15 @@ char* IZ_MIDIGetNoteName(u8 midi_note) { const u8 pitch_class = midi_note % 12; const u8 octave = midi_note / 12; - static char note_name[4]; - sprintf_s(note_name, 4, "%s%u", pitch_names[pitch_class], octave); + static char note_name[8]; + sprintf_s(note_name, 8, "%s%u", pitch_names[pitch_class], octave); return note_name; } u8 IZ_MIDIGetNoteFromName(char* name) { - char name_copy[4]; - memcpy_s(name_copy, 4, name, 4); - _strlwr_s(name_copy, 4); + char name_copy[8]; + memcpy_s(name_copy, 8, name, 8); + _strlwr_s(name_copy, 8); u8 octave; const char base_pitch_name[] = "c d ef g a b"; @@ -181,7 +181,23 @@ IZ_ProcedureResult IZ_MIDIInputInitialize(const char* config_path, IZ_MIDIInputS } u8 player_index; - for (player_index = 0; player_index < PLAYERS; player_index += 1) { + // TODO Pm_CountDevices(), only filter input devices + u8 midi_devices_count = Pm_CountDevices(); + u8 input_midi_devices_count = 0; + u8 device_index; + for (device_index = 0; device_index < midi_devices_count; device_index += 1) { + if (!Pm_GetDeviceInfo(device_index)->output) { + continue; + } + + // midi device can output messages for the app to receive + input_midi_devices_count += 1; + } + + for (player_index = 0; player_index < input_midi_devices_count; player_index += 1) { + if (player_index >= PLAYERS) { + break; + } (*state)[player_index].device_info = Pm_GetDeviceInfo((*state)[player_index].config.device_id); (*state)[player_index].stream = NULL; Pm_OpenInput( diff --git a/src/packages/game/input/IZ_midi.h b/src/packages/game/input/IZ_midi.h index 77db627..40c5969 100644 --- a/src/packages/game/input/IZ_midi.h +++ b/src/packages/game/input/IZ_midi.h @@ -3,7 +3,12 @@ #include #include + +#ifndef PORTMIDI_INCLUDED +#define PORTMIDI_INCLUDED #include +#endif + #include #include "IZ_action.h" @@ -77,8 +82,8 @@ static const IZ_MIDIInputState IZ_DEFAULT_MIDI_INPUT_STATE[PLAYERS] = { 56, 57, }, - .device_id = 0, - .channel = 0, + .device_id = 1, + .channel = 1, }, .event_buffer = {}, .stream = NULL, diff --git a/src/packages/game/input/input.test.c b/src/packages/game/input/input.test.c index f1b0c2f..b39d74c 100644 --- a/src/packages/game/input/input.test.c +++ b/src/packages/game/input/input.test.c @@ -2,8 +2,10 @@ #include "../../../__mocks__/SDL_joystick.mock.h" #include "../../../__mocks__/SDL_stdinc.mock.h" #include "../../../__mocks__/minIni.mock.h" +#include "../../../__mocks__/portmidi.mock.h" #include "IZ_keyboard.h" #include "IZ_joystick.h" +#include "IZ_midi.h" i16 GenerateAxisValueWithinThreshold(u16 threshold) { return rand() % threshold; @@ -15,6 +17,76 @@ i16 GenerateAxisValueOutsideThreshold(u16 threshold) { spec("input") { describe("joystick") { + describe("Initialize") { + static IZ_JoystickState state[PLAYERS]; + + after_each() { + mock_reset(SDL_memcpy); + } + + after_each() { + mock_reset(SDL_NumJoysticks); + } + + after_each() { + mock_reset(SDL_JoystickOpen); + } + + after_each() { + mock_reset(ini_putl); + } + + after_each() { + mock_reset(ini_getl); + } + + it("sets initial state") { + IZ_JoystickInitialize("config.ini", &state); + + check(mock_is_called(SDL_memcpy), "Initial state not loaded."); + check(mock_is_called(SDL_NumJoysticks), "Connected joysticks not checked."); + } + + it("calls load method") { + mock_set_expected_calls(ini_getl, ((CONTROLS - 4) + 2) * PLAYERS); + + IZ_JoystickInitialize("config.ini", &state); + + 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) + ); + } + + it("calls save method") { + mock_set_expected_calls(ini_putl, ((CONTROLS - 4) + 2) * PLAYERS); + + IZ_JoystickInitialize("config.ini", &state); + + 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) + ); + } + + it("opens device handles") { + mock_set_expected_calls(SDL_JoystickOpen, MOCK_OPEN_JOYSTICKS); + + IZ_JoystickInitialize("config.ini", &state); + + check( + mock_get_expected_calls(SDL_JoystickOpen) == mock_get_actual_calls(SDL_JoystickOpen), + "Call count mismatch for SDL_JoystickOpen() (expected %u, received %u).", + mock_get_expected_calls(SDL_JoystickOpen), + mock_get_actual_calls(SDL_JoystickOpen) + ); + } + } + describe("HandleEvents") { static SDL_Event e; static IZ_JoystickState state[PLAYERS] = {}; @@ -289,9 +361,93 @@ spec("input") { ); } } + + describe("Teardown") { + static SDL_Joystick device; + static IZ_JoystickState state[PLAYERS] = {}; + + before_each() { + for (u8 p = 0; p < PLAYERS; p += 1) { + state[p].device = &device; + } + } + + after_each() { + mock_reset(SDL_JoystickClose); + } + + it("closes opened devices") { + mock_set_expected_calls(SDL_JoystickClose, PLAYERS); + + IZ_JoystickTeardown(&state); + + check( + mock_get_expected_calls(SDL_JoystickClose) == mock_get_actual_calls(SDL_JoystickClose), + "Call count mismatch for SDL_JoystickClose() (expected %u, received %u).", + mock_get_expected_calls(SDL_JoystickClose), + mock_get_actual_calls(SDL_JoystickClose) + ); + } + } } describe("keyboard") { + describe("Initialize") { + static IZ_KeyboardState state[PLAYERS] = {}; + + after_each() { + mock_reset(SDL_memcpy); + } + + after_each() { + mock_reset(ini_gets); + } + + after_each() { + mock_reset(ini_puts); + } + + before_each() { + for (u8 p = 0; p < PLAYERS; p += 1) { + for (u8 i = 0; i < CONTROLS; i += 1) { + state[p].config.control_mapping[i] = IZ_DEFAULT_KEYBOARD_STATE[p].config.control_mapping[i]; + } + } + } + + it("sets initial state") { + IZ_KeyboardInitialize("config.ini", &state); + + check(mock_is_called(SDL_memcpy), "Initial state not loaded."); + } + + it("calls load method") { + mock_set_expected_calls(ini_gets, CONTROLS * PLAYERS); + + IZ_KeyboardInitialize("config.ini", &state); + + 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) + ); + } + + it("calls save method") { + mock_set_expected_calls(ini_puts, CONTROLS * PLAYERS); + + IZ_KeyboardInitialize("config.ini", &state); + + 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) + ); + } + } + describe("HandleEvents") { static SDL_Event e; static IZ_KeyboardState state[PLAYERS] = {}; @@ -359,4 +515,197 @@ spec("input") { } } } + + describe("midi") { + describe("Initialize") { + static IZ_MIDIInputState state[PLAYERS]; + + after_each() { + mock_reset(SDL_memcpy); + } + + after_each() { + mock_reset(Pm_CountDevices); + } + + after_each() { + mock_reset(Pm_OpenInput); + } + + after_each() { + mock_reset(ini_puts); + } + + after_each() { + mock_reset(ini_gets); + } + + after_each() { + mock_reset(ini_putl); + } + + after_each() { + mock_reset(ini_getl); + } + + it("sets initial state") { + IZ_MIDIInputInitialize("config.ini", &state); + + check(mock_is_called(SDL_memcpy), "Initial state not loaded."); + check(mock_is_called(Pm_CountDevices), "Connected MIDI devices not checked."); + } + + it("calls load method") { + mock_set_expected_calls(ini_gets, CONTROLS * PLAYERS); + mock_set_expected_calls(ini_getl, 2 * PLAYERS); + + IZ_MIDIInputInitialize("config.ini", &state); + + 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) + ); + + 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) + ); + } + + it("calls save method") { + mock_set_expected_calls(ini_puts, CONTROLS * PLAYERS); + mock_set_expected_calls(ini_putl, 2 * PLAYERS); + + IZ_MIDIInputInitialize("config.ini", &state); + + 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) + ); + + 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) + ); + } + + it("opens device handles") { + mock_set_expected_calls(Pm_OpenInput, MOCK_OPEN_JOYSTICKS); + + IZ_MIDIInputInitialize("config.ini", &state); + + check( + mock_get_expected_calls(Pm_OpenInput) == mock_get_actual_calls(Pm_OpenInput), + "Call count mismatch for Pm_OpenInput() (expected %u, received %u).", + mock_get_expected_calls(Pm_OpenInput), + mock_get_actual_calls(Pm_OpenInput) + ); + } + } + + describe("SaveConfig") { + static IZ_MIDIInputState state[PLAYERS]; + + after_each() { + mock_reset(ini_puts); + } + + after_each() { + mock_reset(ini_putl); + } + + it("calls save method") { + mock_set_expected_calls(ini_puts, CONTROLS * PLAYERS); + mock_set_expected_calls(ini_putl, 2 * PLAYERS); + + IZ_MIDIInputSaveConfig("config.ini", &state); + + 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) + ); + + 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("HandleEvents") { + static PmEvent e; + static IZ_MIDIInputState state[PLAYERS] = {}; + static IZ_Action action[PLAYERS] = {}; + + for (u8 p = 0; p < PLAYERS; p += 1) { + describe("on player %u", p) { + for (u8 i = 0; i < CONTROLS; i += 1) { + it("handles %s action activation", ACTION_NAMES[i]) { + e.message = IZ_MIDI_NOTE_ON | (IZ_DEFAULT_MIDI_INPUT_STATE[p].config.control_mapping[i] << 8); + state[p].config.control_mapping[i] = IZ_DEFAULT_MIDI_INPUT_STATE[p].config.control_mapping[i]; + action[p] = 0; + + IZ_MIDIInputHandleEvents(e, &state, &action); + check( + action[p] == (0x1 << i), + "Action not set." + ); + } + + it("handles %s action deactivation", ACTION_NAMES[i]) { + e.message = IZ_MIDI_NOTE_OFF | (IZ_DEFAULT_MIDI_INPUT_STATE[p].config.control_mapping[i] << 8); + state[p].config.control_mapping[i] = IZ_DEFAULT_MIDI_INPUT_STATE[p].config.control_mapping[i]; + action[p] = ~0; + + IZ_MIDIInputHandleEvents(e, &state, &action); + check( + !(action[p] & (0x1 << i)), + "Action not unset." + ); + } + } + } + } + } + + describe("Teardown") { + static PmStream* stream; + static IZ_MIDIInputState state[PLAYERS] = {}; + + before_each() { + for (u8 p = 0; p < PLAYERS; p += 1) { + state[p].stream = &stream; + } + } + + after_each() { + mock_reset(Pm_Close); + } + + it("closes opened devices") { + mock_set_expected_calls(Pm_Close, PLAYERS); + + IZ_MIDIInputTeardown(&state); + + check( + mock_get_expected_calls(Pm_Close) == mock_get_actual_calls(Pm_Close), + "Call count mismatch for Pm_Close() (expected %u, received %u).", + mock_get_expected_calls(Pm_Close), + mock_get_actual_calls(Pm_Close) + ); + } + } + } }