Add generated tests to verify libx52 functionality

This change adds a suite of tests in JSON format using a Python script
to generate the cmocka based test program. Because we need to wrap some
of libusb functionality, we need to rebuild and relink the libx52
sources with the -Wl,--wrap option.
pull/22/head
nirenjan 2020-06-17 15:06:19 -07:00
parent bf9b1bdfbd
commit 9f37cde784
3 changed files with 2152 additions and 0 deletions

View File

@ -33,11 +33,26 @@ pkgconfig_DATA = libx52.pc
if HAVE_CMOCKA
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
TESTS = libx52test
check_PROGRAMS = libx52test
libx52test_SOURCES = test_libx52.c $(libx52_la_SOURCES)
libx52test_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR='"$(localedir)"' -I $(top_srcdir)
libx52test_CFLAGS += -DGENERATED_TESTS='"test_libx52.c"'
libx52test_LDFLAGS = -Wl,--wrap=libusb_control_transfer @CMOCKA_LIBS@ @LIBUSB_LIBS@
libx52test_LDADD = libx52.la
CLEANFILES = test_libx52.c
test_libx52.c: $(srcdir)/x52_test_gen.py $(srcdir)/x52_tests.json
$(AM_V_GEN) $(PYTHON) $(srcdir)/x52_test_gen.py $(srcdir)/x52_tests.json > $@
endif
# Extra files that need to be in the distribution
EXTRA_DIST = libx52.h x52_commands.h x52_common.h README.md
# Add test files to the distribution
EXTRA_DIST += x52_test_gen.py x52_tests.json
# Add documentation files to the distribution
EXTRA_DIST += \
doc/main.dox \

View File

@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""libx52 test generator program, writes test program to stdout"""
import sys
import json
_TEST_FILE_HEADER = """/*
* libx52 test program
*
* This file is automatically generated. DO NOT EDIT!
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include <stdio.h>
#include "x52_common.h"
static int group_setup(void **state)
{
libx52_device *dev;
int rc;
rc = libx52_init(&dev);
if (rc != LIBX52_SUCCESS) {
return rc;
}
/* Disconnect any potentially connected joysticks */
(void)libx52_disconnect(dev);
/* Create a dummy handle so that libx52_update doesn't abort early */
dev->hdl = (void *)(uintptr_t)(-1);
*state = dev;
return 0;
}
static int group_teardown(void **state)
{
libx52_device *dev = *state;
dev->hdl = NULL;
libx52_exit(dev);
return 0;
}
static int test_setup(void **state)
{
libx52_device *dev = *state;
void *context = dev->ctx;
void *handle = dev->hdl;
memset(dev, 0, sizeof(*dev));
dev->ctx = context;
dev->hdl = handle;
/* Set flags to 1 to indicate that we are testing X52 Pro */
dev->flags = 1;
return 0;
}
int __wrap_libusb_control_transfer(libusb_device_handle *dev_handle,
uint8_t request_type,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
unsigned char *data,
uint16_t wLength,
unsigned int timeout)
{
function_called();
check_expected(wIndex);
check_expected(wValue);
assert_int_equal(request_type,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT);
assert_int_equal(bRequest, 0x91);
assert_null(data);
assert_int_equal(wLength, 0);
assert_int_equal(timeout, 5000);
return mock();
}
"""
_TEST_FUNCTION_HEADER = """
static void {}(void **state)
{{
libx52_device *dev = *state;
int rc;
"""
_TEST_FUNCTION_FOOTER_NORMAL = """
assert_int_equal(rc, LIBX52_SUCCESS);
rc = libx52_update(dev);
assert_int_equal(rc, LIBX52_SUCCESS);
}
"""
_TEST_FUNCTION_FOOTER_ERROR = """
assert_int_equal(rc, LIBX52_ERROR_{});
}}
"""
class Test():
"""Test case class, single test"""
def __init__(self, group, obj):
"""Load test case from an object"""
self.function = group.function
self.name = group.name
self.params_prefix = group.params_prefix
self.params = obj["params"]
if len(self.params_prefix) < len(self.params):
self.params_prefix.extend([''] * (len(self.params) - len(self.params_prefix)))
self.output = obj.get("output", [])
self.retval = obj.get("retval", "")
def definition(self):
test_name = self.name + '_' + '_'.join(p.strip('"') for p in self.params)
return test_name.lower()
def print(self):
print(_TEST_FUNCTION_HEADER.format(self.definition()))
if self.output:
print(" expect_function_calls(__wrap_libusb_control_transfer, {});".format(len(self.output)))
print(" will_return_count(__wrap_libusb_control_transfer, LIBUSB_SUCCESS, {});".format(len(self.output)))
for idx, val in self.output:
print(" expect_value(__wrap_libusb_control_transfer, wIndex, 0x{});".format(idx))
print(" expect_value(__wrap_libusb_control_transfer, wValue, 0x{});".format(val))
params = ', '.join(''.join(p) for p in zip(self.params_prefix, self.params))
print(" rc = {}(dev, {});".format(self.function, params))
if self.retval:
print(_TEST_FUNCTION_FOOTER_ERROR.format(self.retval))
else:
print(_TEST_FUNCTION_FOOTER_NORMAL);
_TEST_GROUP_HEADER = "const struct CMUnitTest tests[] = {"
_TEST_GROUP_FOOTER = """};
"""
class TestGroup():
"""Test group class, contains multiple tests"""
def __init__(self, name, obj):
"""Load test cases from an object"""
self.name = name
self.function = obj["function"]
self.setup_hook = obj.get("setup_hook")
self.params_prefix = obj.get("params_prefix", [])
self.tests = []
for test in obj["tests"]:
self.tests.append(Test(self, test))
def definition(self):
return self.name.lower() + "_tests"
def print(self):
"""Print the test group"""
# Print the test definitions first
for test in self.tests:
test.print()
# Print the list of test cases
# print(_TEST_GROUP_HEADER.format(self.definition()))
# for test in self.tests:
# print(" cmocka_unit_test_setup({}, test_setup),".format(test.definition()))
# print(_TEST_GROUP_FOOTER)
_MAIN_HEADER = """
int main(void)
{
cmocka_set_message_output(CM_OUTPUT_TAP);
"""
_MAIN_FOOTER = """
return 0;
}
"""
_MAIN = """
int main(void)
{
cmocka_set_message_output(CM_OUTPUT_TAP);
cmocka_run_group_tests(tests, group_setup, group_teardown);
return 0;
}
"""
class TestSuite():
"""Test suite class, contains multiple test cases"""
def __init__(self, file):
"""Load test suite"""
with open(file, 'r') as infile:
self.data = json.load(infile)
self.groups = []
for group, obj in self.data.items():
self.groups.append(TestGroup(group, obj))
def print(self):
print(_TEST_FILE_HEADER)
for group in self.groups:
group.print()
print(_TEST_GROUP_HEADER)
for group in self.groups:
for test in group.tests:
print(" cmocka_unit_test_setup({}, test_setup),".format(test.definition()))
print(_TEST_GROUP_FOOTER)
print(_MAIN)
# print(_MAIN_HEADER)
# for group in self.groups:
# print(' cmocka_run_group_tests_name("{}", {}, group_setup, group_teardown);'.format(
# group.name, group.definition()))
# print(_MAIN_FOOTER)
def main():
if len(sys.argv) != 2:
sys.stderr.write('Usage: %s <test-definitions>\n' %
sys.argv[0])
sys.exit(1)
TestSuite(sys.argv[1]).print()
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff