/* * Saitek X52 Pro MFD & LED driver - Profile module tests * * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) * * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 */ #include "config.h" #include #include #include #include #include #include #include #include "libevdev/libevdev.h" #include "libx52io.h" #define PINELOG_MODULE X52D_MOD_PROFILE #include "pinelog.h" #include "x52d_config.h" #include "x52d_const.h" #include "x52d_keyboard.h" #include "x52d_profile.h" /* Stub keyboard: record (key_code, value) for assertions */ #define MAX_RECORDED 32 static struct { uint16_t code; int value; } key_record[MAX_RECORDED]; static size_t key_record_n; int x52d_keyboard_evdev_key(uint16_t key_code, int value) { if (key_record_n < MAX_RECORDED) { key_record[key_record_n].code = key_code; key_record[key_record_n].value = value; key_record_n++; } return 0; } bool x52d_keyboard_evdev_available(void) { return true; } static void key_record_reset(void) { key_record_n = 0; } static int key_code_from_name(const char *name) { int c = libevdev_event_code_from_name(EV_KEY, name); return (c >= 0) ? c : -1; } static void write_profile_file(const char *path, const char *content) { FILE *f = fopen(path, "w"); assert_non_null(f); assert_true(fputs(content, f) >= 0); fclose(f); } static void test_profile_single_key(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_FIRE = key KEY_E\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; report.button[LIBX52IO_BTN_FIRE] = true; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 1); assert_int_equal(key_record[0].code, key_code_from_name("KEY_E")); assert_int_equal(key_record[0].value, 1); memcpy(&prev, &report, sizeof(report)); report.button[LIBX52IO_BTN_FIRE] = false; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 1); assert_int_equal(key_record[0].code, key_code_from_name("KEY_E")); assert_int_equal(key_record[0].value, 0); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_key_combo(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_A = key KEY_LEFTCTRL KEY_X\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; report.button[LIBX52IO_BTN_A] = true; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 2); assert_int_equal(key_record[0].code, key_code_from_name("KEY_LEFTCTRL")); assert_int_equal(key_record[0].value, 1); assert_int_equal(key_record[1].code, key_code_from_name("KEY_X")); assert_int_equal(key_record[1].value, 1); memcpy(&prev, &report, sizeof(report)); report.button[LIBX52IO_BTN_A] = false; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 2); assert_int_equal(key_record[0].code, key_code_from_name("KEY_X")); assert_int_equal(key_record[0].value, 0); assert_int_equal(key_record[1].code, key_code_from_name("KEY_LEFTCTRL")); assert_int_equal(key_record[1].value, 0); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_macro_button_down_only(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_T1_UP = macro KEY_A KEY_B\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; report.button[LIBX52IO_BTN_T1_UP] = true; key_record_reset(); x52d_profile_apply(&report, &prev); x52d_profile_macro_wait_drained(); assert_int_equal(key_record_n, 4); assert_int_equal(key_record[0].code, key_code_from_name("KEY_A")); assert_int_equal(key_record[0].value, 1); assert_int_equal(key_record[1].code, key_code_from_name("KEY_A")); assert_int_equal(key_record[1].value, 0); assert_int_equal(key_record[2].code, key_code_from_name("KEY_B")); assert_int_equal(key_record[2].value, 1); assert_int_equal(key_record[3].code, key_code_from_name("KEY_B")); assert_int_equal(key_record[3].value, 0); memcpy(&prev, &report, sizeof(report)); report.button[LIBX52IO_BTN_T1_UP] = false; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 0); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_macro_pipe_combo(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_T1_UP = macro KEY_LEFTCTRL KEY_C | KEY_A\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; report.button[LIBX52IO_BTN_T1_UP] = true; key_record_reset(); x52d_profile_apply(&report, &prev); x52d_profile_macro_wait_drained(); /* Step 1: Ctrl+C (down order: Ctrl, C; up order: C, Ctrl). Step 2: A (down, up) */ assert_int_equal(key_record_n, 6); assert_int_equal(key_record[0].code, key_code_from_name("KEY_LEFTCTRL")); assert_int_equal(key_record[0].value, 1); assert_int_equal(key_record[1].code, key_code_from_name("KEY_C")); assert_int_equal(key_record[1].value, 1); assert_int_equal(key_record[2].code, key_code_from_name("KEY_C")); assert_int_equal(key_record[2].value, 0); assert_int_equal(key_record[3].code, key_code_from_name("KEY_LEFTCTRL")); assert_int_equal(key_record[3].value, 0); assert_int_equal(key_record[4].code, key_code_from_name("KEY_A")); assert_int_equal(key_record[4].value, 1); assert_int_equal(key_record[5].code, key_code_from_name("KEY_A")); assert_int_equal(key_record[5].value, 0); memcpy(&prev, &report, sizeof(report)); report.button[LIBX52IO_BTN_T1_UP] = false; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 0); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_fallback_mode2_to_mode1(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_FIRE = key KEY_E\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 2; report.button[LIBX52IO_BTN_FIRE] = true; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 1); assert_int_equal(key_record[0].code, key_code_from_name("KEY_E")); assert_int_equal(key_record[0].value, 1); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_shift_layer(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Profile]\n" "ShiftButton=BTN_PINKY\n" "[Mode1]\n" "Button.BTN_FIRE = key KEY_E\n" "[Mode1.Shift]\n" "Button.BTN_FIRE = key KEY_F\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; report.button[LIBX52IO_BTN_PINKY] = true; report.button[LIBX52IO_BTN_FIRE] = true; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 1); assert_int_equal(key_record[0].code, key_code_from_name("KEY_F")); assert_int_equal(key_record[0].value, 1); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_get_name(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; const char *name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Profile]\n" "Name=My Test Profile\n" "[Mode1]\n" "Button.BTN_FIRE = key KEY_E\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); name = x52d_profile_get_name(); assert_non_null(name); assert_string_equal(name, "My Test Profile"); x52d_profile_exit(); name = x52d_profile_get_name(); assert_null(name); unlink(path); rmdir(tmpdir); } static void test_profile_action_name_key(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report, prev; const char *action_name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_FIRE = key KEY_E name \"Primary Fire\"\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); memset(&prev, 0, sizeof(prev)); report.mode = 1; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE); assert_non_null(action_name); assert_string_equal(action_name, "Primary Fire"); report.button[LIBX52IO_BTN_FIRE] = true; key_record_reset(); x52d_profile_apply(&report, &prev); assert_int_equal(key_record_n, 1); assert_int_equal(key_record[0].code, key_code_from_name("KEY_E")); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_action_name_unquoted(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report; const char *action_name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_A = key KEY_X name YawLeft\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); report.mode = 1; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_A); assert_non_null(action_name); assert_string_equal(action_name, "YawLeft"); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_action_name_none(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report; const char *action_name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_FIRE = key KEY_E\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); report.mode = 1; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE); assert_null(action_name); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_action_name_shift_layer(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report; const char *action_name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Profile]\n" "ShiftButton=BTN_PINKY\n" "[Mode1]\n" "Button.BTN_FIRE = key KEY_E name Unshifted\n" "[Mode1.Shift]\n" "Button.BTN_FIRE = key KEY_F name Shifted\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); report.mode = 1; report.button[LIBX52IO_BTN_PINKY] = false; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE); assert_non_null(action_name); assert_string_equal(action_name, "Unshifted"); report.button[LIBX52IO_BTN_PINKY] = true; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE); assert_non_null(action_name); assert_string_equal(action_name, "Shifted"); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } static void test_profile_action_name_macro(void **state) { char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX"; char path[256]; libx52io_report report; const char *action_name; (void)state; assert_non_null(mkdtemp(tmpdir)); snprintf(path, sizeof(path), "%s/test.conf", tmpdir); write_profile_file(path, "[Mode1]\n" "Button.BTN_T1_UP = macro KEY_A KEY_B name \"Simple macro\"\n"); x52d_config_set("Profiles", "Directory", tmpdir); x52d_config_set("Profiles", "Profile", "test"); x52d_profile_init(); memset(&report, 0, sizeof(report)); report.mode = 1; action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_T1_UP); assert_non_null(action_name); assert_string_equal(action_name, "Simple macro"); x52d_profile_exit(); unlink(path); rmdir(tmpdir); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_profile_single_key), cmocka_unit_test(test_profile_key_combo), cmocka_unit_test(test_profile_macro_button_down_only), cmocka_unit_test(test_profile_macro_pipe_combo), cmocka_unit_test(test_profile_fallback_mode2_to_mode1), cmocka_unit_test(test_profile_shift_layer), cmocka_unit_test(test_profile_get_name), cmocka_unit_test(test_profile_action_name_key), cmocka_unit_test(test_profile_action_name_unquoted), cmocka_unit_test(test_profile_action_name_none), cmocka_unit_test(test_profile_action_name_shift_layer), cmocka_unit_test(test_profile_action_name_macro), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); }