diff --git a/lib/libx52/Makefile.am b/lib/libx52/Makefile.am index ea15b66..d0d12a3 100644 --- a/lib/libx52/Makefile.am +++ b/lib/libx52/Makefile.am @@ -30,17 +30,21 @@ x52include_HEADERS = libx52.h # pkg-config files pkgconfig_DATA = libx52.pc -check_PROGRAMS = test_offset +check_PROGRAMS = test_offset test_led test_offset_SOURCES = test_offset.c test_offset_CFLAGS = @LIBUSB_CFLAGS@ test_offset_LDADD = libx52.la +test_led_SOURCES = test_led.c test_common.c +test_led_CFLAGS = @LIBUSB_CFLAGS@ +test_led_LDADD = libx52.la + LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh -TESTS = test_offset +TESTS = test_offset test_led # Extra files that need to be in the distribution -EXTRA_DIST = libx52.h x52_commands.h x52_common.h README.md +EXTRA_DIST = libx52.h x52_commands.h x52_common.h test_common.h README.md # Add documentation files to the distribution EXTRA_DIST += \ diff --git a/lib/libx52/test_common.c b/lib/libx52/test_common.c new file mode 100644 index 0000000..561408d --- /dev/null +++ b/lib/libx52/test_common.c @@ -0,0 +1,133 @@ +/* + * Saitek X52 Pro MFD & LED driver + * Common functionality for test programs + * + * Copyright (C) 2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "x52_common.h" +#include "test_common.h" + +#define MAX_DIAGS 32 +#define MAX_DIAG_SZ 256 +static int diag_count; +static char diagnostic[MAX_DIAGS][MAX_DIAG_SZ]; + +#define ADD_DIAG(fmt_str, ...) do { \ + if (diag_count < MAX_DIAGS) { \ + snprintf(diagnostic[diag_count], MAX_DIAG_SZ, fmt_str, ##__VA_ARGS__); \ + diag_count++; \ + } \ +} while(0) + +/* Test vendor command function */ +int x52_test_vendor_command(libx52_device *dev, uint16_t index, uint16_t value) +{ + struct x52_vendor_data *vdata = (struct x52_vendor_data *)dev->hdl; + struct ivpair data = {index, value}; + + if (vdata->written < MAX_SZ) { + vdata->data[vdata->written] = data; + vdata->written++; + } + + return LIBX52_SUCCESS; +} + +/* Check expected data */ +bool x52_test_assert_expected(libx52_device *dev, struct ivpair *data) +{ + int written = 0; + struct x52_vendor_data *vdata = (struct x52_vendor_data *)dev->hdl; + + while(data->index != 0 && data->value != 0 && written < vdata->written) { + if ((data->index != vdata->data[written].index) || + (data->value != vdata->data[written].value)) { + ADD_DIAG("Mismatched data at position %d:", written); + ADD_DIAG("\tExpected: {%04x, %04x}", data->index, data->value); + ADD_DIAG("\tObserved: {%04x, %04x}", vdata->data[written].index, vdata->data[written].value); + return false; + } + + data++; + written++; + } + + if (data->index != 0 || data->value != 0) { + ADD_DIAG("Insufficient data written, got only %d, additional expected:", written); + while (data->index != 0 && data->value != 0) { + ADD_DIAG("\t%04x %04x", data->index, data->value); + data++; + } + return false; + } + + if (vdata->written > written) { + ADD_DIAG("More data written, expected only %d, got %d", written, vdata->written); + return false; + } + + return true; +} + +void x52_test_print_diagnostics(void) +{ + int i; + for (i = 0; i < diag_count; i++) { + printf("# %s\n", diagnostic[i]); + } +} + +/* + * Initialize libx52, close any device handles, create a dummy handle + * and override the vendor command function. + */ +libx52_device *x52_test_init(void) +{ + libx52_device *dev; + struct x52_vendor_data *vdata; + int rc; + + rc = libx52_init(&dev); + if (rc != LIBX52_SUCCESS) { + fputs(libx52_strerror(rc), stderr); + exit(1); + } + + (void)libx52_disconnect(dev); + + /* Allocate memory for vendor data */ + vdata = calloc(1, sizeof(*vdata)); + if (vdata == NULL) { + perror("vendor data calloc"); + libx52_exit(dev); + return NULL; + } + + /* Reset the diagnostics buffers */ + memset(diagnostic, 0, sizeof(diagnostic)); + diag_count = 0; + + /* We don't need the device handle in test code, repurpose it */ + dev->hdl = (libusb_device_handle *)vdata; + + /* Setup vendor command function */ + dev->vendor_cmd_fn = x52_test_vendor_command; + + return dev; +} + +void x52_test_cleanup(libx52_device *dev) +{ + free(dev->hdl); + dev->hdl = NULL; + libx52_exit(dev); +} diff --git a/lib/libx52/test_common.h b/lib/libx52/test_common.h new file mode 100644 index 0000000..2094d5b --- /dev/null +++ b/lib/libx52/test_common.h @@ -0,0 +1,48 @@ +/* + * Saitek X52 Pro MFD & LED driver + * Common functionality for test programs + * + * Copyright (C) 2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#ifndef _TEST_COMMON_H +#define _TEST_COMMON_H +#include +#include +#include "x52_common.h" + +/* ivpair is a pair of index and value fields that are passed to the + * test vendor command function. + */ +struct ivpair { + uint16_t index; + uint16_t value; +}; + +#define MAX_SZ 100 +struct x52_vendor_data { + int written; + struct ivpair data[MAX_SZ]; +}; + +/* + * Initialize libx52, close any device handles, create a dummy handle + * and override the vendor command function. + */ +libx52_device *x52_test_init(void); + +/* + * Check if expected data matches with written data. Terminate expected + * data with a pair of NULLs + */ +bool x52_test_assert_expected(libx52_device *dev, struct ivpair *data); + +/* Print diagnostics to screen */ +void x52_test_print_diagnostics(void); + +/* Cleanup test data */ +void x52_test_cleanup(libx52_device *dev); + +#endif // !defined _TEST_COMMON_H diff --git a/lib/libx52/test_led.c b/lib/libx52/test_led.c new file mode 100644 index 0000000..7356c2f --- /dev/null +++ b/lib/libx52/test_led.c @@ -0,0 +1,150 @@ +/* + * Saitek X52 Pro MFD & LED driver + * Test program for validating LED sets + * + * Copyright (C) 2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include +#include "x52_common.h" +#include "test_common.h" +#include "x52_commands.h" + +struct test_case { + const char *test_case_id; + libx52_led_id led_id; + libx52_led_state state; + int retval; + struct ivpair data[3]; +}; + +#define X52_LED_CMD 0xb8 +#define UNSUPPORTED(led, state) { #led "/" #state " unsupported", LIBX52_LED_ ## led, LIBX52_LED_STATE_ ## state, LIBX52_ERROR_NOT_SUPPORTED} +#define OFF_MONO(led) { #led "/Off", LIBX52_LED_## led, LIBX52_LED_STATE_OFF, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led) << 8)}, {0, 0}}} +#define ON(led) { #led "/On", LIBX52_LED_## led, LIBX52_LED_STATE_ON, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led) << 8) | 1}, {0, 0}}} +#define OFF_COLOR(led) { #led "/Off", LIBX52_LED_## led, LIBX52_LED_STATE_OFF, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led + 0) << 8)}, {X52_LED_CMD, ((LIBX52_LED_ ## led + 1) << 8)}, {0, 0}}} +#define RED(led) { #led "/Red", LIBX52_LED_## led, LIBX52_LED_STATE_RED, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led + 0) << 8) | 1}, {X52_LED_CMD, ((LIBX52_LED_ ## led + 1) << 8) | 0}, {0, 0}}} +#define AMBER(led) { #led "/Amber", LIBX52_LED_## led, LIBX52_LED_STATE_AMBER, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led + 0) << 8) | 1}, {X52_LED_CMD, ((LIBX52_LED_ ## led + 1) << 8) | 1}, {0, 0}}} +#define GREEN(led) { #led "/Green", LIBX52_LED_## led, LIBX52_LED_STATE_GREEN, LIBX52_SUCCESS, {{X52_LED_CMD, ((LIBX52_LED_ ## led + 0) << 8) | 0}, {X52_LED_CMD, ((LIBX52_LED_ ## led + 1) << 8) | 1}, {0, 0}}} + +const struct test_case test_cases[] = { + OFF_MONO(FIRE), + ON(FIRE), + UNSUPPORTED(FIRE, RED), + UNSUPPORTED(FIRE, AMBER), + UNSUPPORTED(FIRE, GREEN), + + OFF_COLOR(A), + UNSUPPORTED(A, ON), + RED(A), + AMBER(A), + GREEN(A), + + OFF_COLOR(B), + UNSUPPORTED(B, ON), + RED(B), + AMBER(B), + GREEN(B), + + OFF_COLOR(D), + UNSUPPORTED(D, ON), + RED(D), + AMBER(D), + GREEN(D), + + OFF_COLOR(E), + UNSUPPORTED(E, ON), + RED(E), + AMBER(E), + GREEN(E), + + OFF_COLOR(T1), + UNSUPPORTED(T1, ON), + RED(T1), + AMBER(T1), + GREEN(T1), + + OFF_COLOR(T2), + UNSUPPORTED(T2, ON), + RED(T2), + AMBER(T2), + GREEN(T2), + + OFF_COLOR(T3), + UNSUPPORTED(T3, ON), + RED(T3), + AMBER(T3), + GREEN(T3), + + OFF_COLOR(POV), + UNSUPPORTED(POV, ON), + RED(POV), + AMBER(POV), + GREEN(POV), + + OFF_COLOR(CLUTCH), + UNSUPPORTED(CLUTCH, ON), + RED(CLUTCH), + AMBER(CLUTCH), + GREEN(CLUTCH), + + OFF_MONO(THROTTLE), + ON(THROTTLE), + UNSUPPORTED(THROTTLE, RED), + UNSUPPORTED(THROTTLE, AMBER), + UNSUPPORTED(THROTTLE, GREEN), +}; + +#define TC_COUNT (sizeof(test_cases) / sizeof(test_cases[0])) + +void run_test(int tc_id) +{ + struct libx52_device *dev = x52_test_init(); + + struct test_case test = test_cases[tc_id]; + + /* Set the X52Pro flag in dev->flags, otherwise libx52_set_led_state will + * always return not supported + */ + dev->flags = 1; + + #define PRINT_FAIL() printf("not ok %d %s\n", tc_id+1, test.test_case_id) + #define PRINT_PASS() printf("ok %d %s\n", tc_id+1, test.test_case_id) + int rc = libx52_set_led_state(dev, test.led_id, test.state); + + if (rc != test.retval) { + PRINT_FAIL(); + printf("# Expected retval %d, got %d\n", test.retval, rc); + return; + } + + rc = libx52_update(dev); + if (rc != LIBX52_SUCCESS) { + PRINT_FAIL(); + printf("# libx52_update failed, rc = %d\n", rc); + return; + } + + if (!x52_test_assert_expected(dev, test.data)) { + PRINT_FAIL(); + x52_test_print_diagnostics(); + return; + } + + PRINT_PASS(); + x52_test_cleanup(dev); +} + +int main() +{ + int i; + + printf("1..%ld\n", TC_COUNT); + for (i = 0; i < TC_COUNT; i++) { + run_test(i); + } +}