/** * @file controller.c * @brief Unit tests for control maps and SDL input dispatch. * * SDL events are synthesized directly rather than pumped through the event * queue, so none of this needs a physical keyboard or gamepad. The device * enumeration helpers are exercised against the dummy drivers, where the * expected outcome is "no devices, no crash". */ #include #include #include #include // akgl/controller.h uses akgl_Actor without declaring it, so actor.h has to // come first, the same way src/controller.c reaches it through akgl/game.h. #include #include #include #include #include #include #include "testutil.h" /* * akgl/controller.h declares these as akgl_controller_handle_*, but src has * always defined them as gamepad_handle_*. Declared here under the names that * actually link, so the tests can reach them without renaming shipped symbols. */ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event); akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event); akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event); akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event); /** @brief Keyboard id the tests bind their control maps to. */ #define TEST_KBID 11 /** @brief Gamepad id the tests bind their control maps to. */ #define TEST_JSID 22 /** @brief Stand-in for the application state pointer SDL hands to callbacks. */ static int appstate_placeholder = 0; /** @brief The actor every control map in this file targets. */ static akgl_Actor *player = NULL; /** @brief Base character supplying the player's acceleration constants. */ static akgl_Character *playerchar = NULL; /** @brief Clear every control map so each test starts from an empty binding set. */ static void reset_control_maps(void) { int i = 0; for ( i = 0; i < AKGL_MAX_CONTROL_MAPS; i++ ) { memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap)); } } /** @brief Register the "player" actor the gamepad handlers look up by name. */ static akerr_ErrorContext *make_player(void) { PREPARE_ERROR(e); ATTEMPT { CATCH(e, akgl_registry_init_actor()); CATCH(e, akgl_registry_init_character()); CATCH(e, akgl_heap_init()); CATCH(e, akgl_heap_next_character(&playerchar)); CATCH(e, akgl_character_initialize(playerchar, "playerchar")); playerchar->ax = 6.0f; playerchar->ay = 8.0f; CATCH(e, akgl_heap_next_actor(&player)); CATCH(e, akgl_actor_initialize(player, "player")); player->basechar = playerchar; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /** @brief Build a synthetic keyboard event. */ static void make_key_event(SDL_Event *event, uint32_t type, SDL_KeyboardID which, SDL_Keycode key) { memset(event, 0x00, sizeof(SDL_Event)); event->type = type; event->key.which = which; event->key.key = key; } /** @brief Build a synthetic gamepad button event. */ static void make_button_event(SDL_Event *event, uint32_t type, SDL_JoystickID which, uint8_t button) { memset(event, 0x00, sizeof(SDL_Event)); event->type = type; event->gbutton.which = which; event->gbutton.button = button; } akerr_ErrorContext *test_controller_pushmap(void) { PREPARE_ERROR(e); akgl_Control control; int i = 0; ATTEMPT { reset_control_maps(); memset(&control, 0x00, sizeof(akgl_Control)); control.key = SDLK_SPACE; control.event_on = SDL_EVENT_KEY_DOWN; control.event_off = SDL_EVENT_KEY_UP; control.handler_on = &akgl_Actor_cmhf_left_on; control.handler_off = &akgl_Actor_cmhf_left_off; TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing one control"); TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 1, "pushing one control left nextMap at %d, expected 1", GAME_ControlMaps[0].nextMap); TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_SPACE, "the pushed control did not land in slot 0"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_left_on, "the pushed control lost its press handler"); // Pushes accumulate rather than overwrite. TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing a second control"); TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2, "pushing a second control left nextMap at %d, expected 2", GAME_ControlMaps[0].nextMap); // Maps are independent of each other. TEST_EXPECT_OK(e, akgl_controller_pushmap(3, &control), "pushing into a different map"); TEST_ASSERT(e, GAME_ControlMaps[3].nextMap == 1, "map 3 nextMap is %d, expected 1", GAME_ControlMaps[3].nextMap); TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2, "pushing into map 3 disturbed map 0 (nextMap %d)", GAME_ControlMaps[0].nextMap); // Filling a map exactly to capacity still succeeds. reset_control_maps(); for ( i = 0; i < AKGL_MAX_CONTROLS; i++ ) { akerr_ErrorContext *pushresult = akgl_controller_pushmap(0, &control); if ( pushresult != NULL ) { pushresult->handled = true; pushresult = akerr_release_error(pushresult); FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "pushing control %d of %d failed", i, AKGL_MAX_CONTROLS); } } TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == AKGL_MAX_CONTROLS, "a full map reports nextMap %d, expected %d", GAME_ControlMaps[0].nextMap, AKGL_MAX_CONTROLS); // One past capacity is refused. TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(0, &control), "pushing into a full control map"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_pushmap(0, NULL), "pushing a NULL control"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(AKGL_MAX_CONTROL_MAPS, &control), "pushing into a control map id at the limit"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(AKGL_MAX_CONTROL_MAPS + 5, &control), "pushing into a control map id past the limit"); } CLEANUP { reset_control_maps(); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_default_bindings(void) { PREPARE_ERROR(e); ATTEMPT { reset_control_maps(); CATCH(e, make_player()); TEST_EXPECT_OK(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID), "installing the default control map"); TEST_ASSERT(e, GAME_ControlMaps[0].target == player, "the default map did not target the player actor"); TEST_ASSERT(e, GAME_ControlMaps[0].kbid == TEST_KBID, "the default map recorded keyboard %d, expected %d", GAME_ControlMaps[0].kbid, TEST_KBID); TEST_ASSERT(e, GAME_ControlMaps[0].jsid == TEST_JSID, "the default map recorded gamepad %d, expected %d", GAME_ControlMaps[0].jsid, TEST_JSID); // Four keyboard bindings then four gamepad bindings. TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 8, "the default map installed %d controls, expected 8", GAME_ControlMaps[0].nextMap); TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_DOWN, "the first default binding is not the down arrow"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_down_on, "the down arrow is not bound to the down handler"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[1].key == SDLK_UP, "the second default binding is not the up arrow"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[2].key == SDLK_LEFT, "the third default binding is not the left arrow"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[3].key == SDLK_RIGHT, "the fourth default binding is not the right arrow"); // The gamepad half binds buttons and leaves the keycode clear, so a // keyboard event cannot accidentally match a dpad binding. TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].button == SDL_GAMEPAD_BUTTON_DPAD_DOWN, "the fifth default binding is not the dpad down button"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].key == 0, "a gamepad binding also carries a keycode"); TEST_ASSERT(e, GAME_ControlMaps[0].controls[7].button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "the eighth default binding is not the dpad right button"); // An unknown actor name is a registry error, not a crash. reset_control_maps(); TEST_EXPECT_STATUS(e, AKGL_ERR_REGISTRY, akgl_controller_default(1, "no_such_actor", TEST_KBID, TEST_JSID), "installing a default map for an unregistered actor"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_default(AKGL_MAX_CONTROL_MAPS, "player", TEST_KBID, TEST_JSID), "installing a default map at a control map id past the limit"); } CLEANUP { reset_control_maps(); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_handle_keyboard_events(void) { PREPARE_ERROR(e); SDL_Event event; ATTEMPT { reset_control_maps(); CATCH(e, make_player()); CATCH(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID)); // Pressing left routes to the left press handler. player->state = 0; make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a left key press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "a left key press did not start the actor moving left (state %d)", player->state); TEST_ASSERT_FEQ(e, player->ax, -6.0f, "a left key press set ax to %f, expected -6", player->ax); // Releasing it routes to the release handler. make_key_event(&event, SDL_EVENT_KEY_UP, TEST_KBID, SDLK_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a left key release"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "a left key release did not stop the actor (state %d)", player->state); // The other three directions route the same way. player->state = 0; make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_RIGHT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a right key press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT), "a right key press did not start the actor moving right (state %d)", player->state); player->state = 0; make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_UP); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching an up key press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_UP), "an up key press did not start the actor moving up (state %d)", player->state); player->state = 0; make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_DOWN); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a down key press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_DOWN), "a down key press did not start the actor moving down (state %d)", player->state); // An unbound key is ignored. player->state = 0; make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_F12); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching an unbound key"); TEST_ASSERT(e, player->state == 0, "an unbound key changed the actor state to %d", player->state); // A bound key from a different keyboard is ignored, so split-keyboard // local multiplayer does not cross-talk. make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID + 1, SDLK_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a bound key from an unbound keyboard"); TEST_ASSERT(e, player->state == 0, "a key from another keyboard changed the actor state to %d", player->state); } CLEANUP { reset_control_maps(); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_handle_gamepad_events(void) { PREPARE_ERROR(e); SDL_Event event; ATTEMPT { reset_control_maps(); CATCH(e, make_player()); CATCH(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID)); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a dpad left press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "a dpad left press did not start the actor moving left (state %d)", player->state); make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_UP, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a dpad left release"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "a dpad left release did not stop the actor (state %d)", player->state); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_UP); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a dpad up press"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_UP), "a dpad up press did not start the actor moving up (state %d)", player->state); // A press from an unbound gamepad is ignored. player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID + 1, SDL_GAMEPAD_BUTTON_DPAD_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching a dpad press from an unbound gamepad"); TEST_ASSERT(e, player->state == 0, "a press from another gamepad changed the actor state to %d", player->state); // An unbound button on the bound gamepad is ignored. make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_START); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching an unbound gamepad button"); TEST_ASSERT(e, player->state == 0, "an unbound gamepad button changed the actor state to %d", player->state); } CLEANUP { reset_control_maps(); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_handle_event_edge_cases(void) { PREPARE_ERROR(e); SDL_Event event; ATTEMPT { reset_control_maps(); CATCH(e, make_player()); // With no control maps installed at all, every event is a no-op. make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_LEFT); TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching into an empty control map table"); // A map in a later slot is still reached, so the scan does not stop at // the first unpopulated entry. CATCH(e, akgl_controller_default(5, "player", TEST_KBID, TEST_JSID)); player->state = 0; TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching to a control map in a later slot"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "a control map in slot 5 was not consulted (state %d)", player->state); // An event type that no binding uses falls through harmlessly. memset(&event, 0x00, sizeof(SDL_Event)); event.type = SDL_EVENT_MOUSE_MOTION; TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event), "dispatching an event type with no bindings"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_event(NULL, &event), "dispatching with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_event(&appstate_placeholder, NULL), "dispatching a NULL event"); } CLEANUP { reset_control_maps(); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_gamepad_button_handlers(void) { PREPARE_ERROR(e); SDL_Event event; ATTEMPT { CATCH(e, make_player()); // These handlers find their actor by looking up "player" in the registry // rather than taking it as an argument. player->state = 0; player->movement_controls_face = false; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad down press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_DOWN), "the dpad down handler did not set MOVING_DOWN (state %d)", player->state); // With automatic facing off, the handler sets the facing itself. TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_FACE_DOWN), "the dpad down handler did not set FACE_DOWN (state %d)", player->state); TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad down release handler"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_DOWN), "the dpad down release handler did not clear MOVING_DOWN (state %d)", player->state); TEST_ASSERT(e, player->curSpriteFrameId == 0, "the release handler did not reset the animation frame"); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_UP); TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad up press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_UP), "the dpad up handler did not set MOVING_UP (state %d)", player->state); TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad up release handler"); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT); TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad left press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "the dpad left handler did not set MOVING_LEFT (state %d)", player->state); TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad left release handler"); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad right press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT), "the dpad right handler did not set MOVING_RIGHT (state %d)", player->state); TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad right release handler"); // With automatic facing on, the handler leaves facing to the actor's own // face function. player->state = 0; player->movement_controls_face = true; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad down press with automatic facing on"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_FACE_DOWN), "the handler set facing even though the actor faces automatically (state %d)", player->state); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_down(NULL, &event), "dpad press handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_down(&appstate_placeholder, NULL), "dpad press handler with a NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_up(NULL, &event), "dpad release handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_up(&appstate_placeholder, NULL), "dpad release handler with a NULL event"); // With no actor named "player" registered there is nothing to drive. CATCH(e, akgl_registry_init_actor()); make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad press handler with no player actor registered"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad release handler with no player actor registered"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_device_events(void) { PREPARE_ERROR(e); SDL_Event event; ATTEMPT { // The dummy drivers present no gamepads, so the add and remove handlers // take their "unknown device" paths. Neither may crash. make_button_event(&event, SDL_EVENT_GAMEPAD_ADDED, 999, 0); TEST_EXPECT_OK(e, gamepad_handle_added(&appstate_placeholder, &event), "handling an add for a device that cannot be opened"); make_button_event(&event, SDL_EVENT_GAMEPAD_REMOVED, 999, 0); TEST_EXPECT_OK(e, gamepad_handle_removed(&appstate_placeholder, &event), "handling a remove for a device that was never open"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_added(NULL, &event), "add handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_added(&appstate_placeholder, NULL), "add handler with a NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_removed(NULL, &event), "remove handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_removed(&appstate_placeholder, NULL), "remove handler with a NULL event"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_controller_device_enumeration(void) { PREPARE_ERROR(e); ATTEMPT { // Enumeration has to succeed on a machine with no input devices, which // is the normal state under the dummy drivers and in CI. TEST_EXPECT_OK(e, akgl_controller_list_keyboards(), "listing keyboards"); TEST_EXPECT_OK(e, akgl_controller_open_gamepads(), "opening gamepads"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } int main(void) { PREPARE_ERROR(errctx); SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy"); SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy"); ATTEMPT { FAIL_ZERO_BREAK( errctx, SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD), AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); CATCH(errctx, test_controller_pushmap()); CATCH(errctx, test_controller_default_bindings()); CATCH(errctx, test_controller_handle_keyboard_events()); CATCH(errctx, test_controller_handle_gamepad_events()); CATCH(errctx, test_controller_handle_event_edge_cases()); CATCH(errctx, test_controller_gamepad_button_handlers()); CATCH(errctx, test_controller_device_events()); CATCH(errctx, test_controller_device_enumeration()); } CLEANUP { SDL_Quit(); } PROCESS(errctx) { } FINISH_NORETURN(errctx); }