This adds a load tester driver test_driver_data a for the new extensible driver_data loader API, part of firmware_class. This test driver enables you to build your tests in userspace by exposing knobs of the exported API to userspace and enables a trigger action to mimic a one time use of the kernel API. This gives us the flexibility to build test case from userspace with less kernel changes.
Signed-off-by: Luis R. Rodriguez <mcg...@kernel.org> --- Documentation/driver-api/firmware/driver_data.rst | 32 + MAINTAINERS | 1 + lib/Kconfig.debug | 12 + lib/Makefile | 1 + lib/test_driver_data.c | 1039 +++++++++++++++++++++ tools/testing/selftests/firmware/Makefile | 2 +- tools/testing/selftests/firmware/config | 1 + tools/testing/selftests/firmware/driver_data.sh | 826 ++++++++++++++++ 8 files changed, 1913 insertions(+), 1 deletion(-) create mode 100644 lib/test_driver_data.c create mode 100755 tools/testing/selftests/firmware/driver_data.sh diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst index 08407b7568fe..757c2ffa4ba6 100644 --- a/Documentation/driver-api/firmware/driver_data.rst +++ b/Documentation/driver-api/firmware/driver_data.rst @@ -68,6 +68,38 @@ When driver_data_file_request_async() completes you can rest assured all the work for both triggering, and processing the driver data using any of your callbacks has completed. +Testing the driver_data API +=========================== + +The driver data API has a selftest driver: lib/test_driver_data.c. The +test_driver_data enables you to build your tests in userspace by exposing knobs +of the exported API in userspace and enabling userspace to configure and +trigger a kernel call. This lets us build most possible test cases of +the kernel APIs from userspace. + +The test_driver_data also enables multiple test triggers to be created +enabling testing to be done in parallel, one test interface per test case. + +To test an async call one could do:: + + echo anything > /lib/firmware/test-driver_data.bin + echo -n 1 > /sys/devices/virtual/misc/test_driver_data0/config_async + echo -n 1 > /sys/devices/virtual/misc/test_driver_data0/trigger_config + +A series of tests have been written to test the driver data API thoroughly. +A respective test case is expected to bet written as new features get added. +For details of existing tests run:: + + tools/testing/selftests/firmware/driver_data.sh -l + +To see all available options:: + + tools/testing/selftests/firmware/driver_data.sh --help + +To run a test 0010 case 40 times:: + + tools/testing/selftests/firmware/driver_data.sh -c 0010 40 + Tracking development enhancements and ideas =========================================== diff --git a/MAINTAINERS b/MAINTAINERS index f1dd84bb21ed..96449804ec9d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5072,6 +5072,7 @@ L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/firmware_class/ F: drivers/base/firmware*.c +F: lib/test_driver_data.c F: include/linux/firmware.h F: include/linux/driver_data.h diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 64c03b07ad2f..68495327ca49 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1975,6 +1975,18 @@ config TEST_FIRMWARE If unsure, say N. +config TEST_DRIVER_DATA + tristate "Test driver data loading via driver_data APIs" + default n + depends on FW_LOADER + help + This builds the "test_driver_data" module that creates a userspace + interface for testing driver data loading using the driver_data API. + This can be used to control the triggering of driver data loading + without needing an actual real device. + + If unsure, say N. + config TEST_UDELAY tristate "udelay test driver" default n diff --git a/lib/Makefile b/lib/Makefile index fbbe2e2dba8f..79eecd0ce6fb 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -46,6 +46,7 @@ obj-y += kstrtox.o obj-$(CONFIG_TEST_BPF) += test_bpf.o obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o obj-$(CONFIG_TEST_HASH) += test_hash.o test_siphash.o +obj-$(CONFIG_TEST_DRIVER_DATA) += test_driver_data.o obj-$(CONFIG_TEST_KASAN) += test_kasan.o obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o obj-$(CONFIG_TEST_LKM) += test_module.o diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c new file mode 100644 index 000000000000..0c2670b356aa --- /dev/null +++ b/lib/test_driver_data.c @@ -0,0 +1,1039 @@ +/* + * Driver data test interface + * + * Copyright (C) 2017 Luis R. Rodriguez <mcg...@kernel.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of copyleft-next (version 0.3.1 or later) as published + * at http://copyleft-next.org/. + * + * This module provides an interface to trigger and test the driver data API + * through a series of configurations and a few triggers. This driver + * lacks any extra dependencies, and will not normally be loaded by the + * system unless explicitly requested by name. You can also build this + * driver into your kernel. + * + * Although all configurations are already written for and will be supported + * for this test driver, ideally we should strive to see what mechanisms we + * can put in place to instead automatically generate this sort of test + * interface, test cases, and infer results. Its a simple enough interface that + * should hopefully enable more exploring in this area. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/completion.h> +#include <linux/driver_data.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/async.h> +#include <linux/delay.h> +#include <linux/vmalloc.h> + +/* Used for the fallback default to test against */ +#define TEST_DRIVER_DATA "test-driver_data.bin" + +/* + * For device allocation / registration + */ +static DEFINE_MUTEX(reg_dev_mutex); +static LIST_HEAD(reg_test_devs); + +/* + * num_test_devs actually represents the *next* ID of the next + * device we will allow to create. + */ +int num_test_devs; + +/** + * test_config - represents configuration for the driver_data API + * + * @name: the name of the primary driver_data file to look for + * @default_name: a fallback example, used to test the optional callback + * mechanism. + * @async: true if you want to trigger an async request. This will use + * driver_data_request_async(). If false the synchronous call will + * be used, driver_data_request(). + * @optional: whether or not the driver_data is optional refer to the + * struct driver_data_reg_params @optional field for more information. + * @keep: whether or not we wish to free the driver_data on our own, refer to + * the struct driver_data_req_params @keep field for more information. + * @enable_opt_cb: whether or not the optional callback should be set + * on a trigger. There is no equivalent setting on the struct + * driver_data_req_params as this is implementation specific, and in + * in driver_data API its explicit if you had defined an optional call + * back for your descriptor with either DRIVER_DATA_SYNC_OPT_CB() or + * DRIVER_DATA_ASYNC_OPT_CB(). Since the params are in a const we have + * no option but to use a flag and two const structs to decide which + * one we should use. + * @test_result: a test may use this to collect the result from the call + * of the driver_data_request_async() or driver_data_request() calls used + * in their tests. Note that for async calls this typically will be a + * successful result (0) unless of course you've used bogus parameters, or + * the system is out of memory. Tests against the callbacks can only be + * implementation specific, so we don't test for that for now but it may + * make sense to build tests cases against a series of semantically + * similar family of callbacks that generally represents usage in the + * kernel. Synchronous calls return bogus error checks against the + * parameters as well, but also return the result of the work from the + * callbacks. You can therefore rely on sync calls if you really want to + * test for the callback results as well. Errors you can expect: + * + * API specific: + * + * 0: success for sync, for async it means request was sent + * -EINVAL: invalid parameters or request + * -ENOENT: files not found + * + * System environment: + * + * -ENOMEM: memory pressure on system + * -ENODEV: out of number of devices to test + * + * The ordering of elements in this struct must match the exact order of the + * elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know + * what corresponding field each device attribute configuration entry maps + * to what struct member on test_alloc_dev_attrs(). + */ +struct test_config { + char *name; + char *default_name; + bool async; + bool optional; + bool keep; + bool enable_opt_cb; + + int test_result; +}; + +/** + * test_driver_data_private - private device driver driver_data representation + * + * @size: size of the data copied, in bytes + * @data: the actual data we copied over from driver_data + * @written: true if a callback managed to copy data over to the device + * successfully. Since different callbacks are used for this purpose + * having the data written does not necessarily mean a test case + * completed successfully. Each tests case has its own specific + * goals. + * + * Private representation of buffer where we put the device system data. + */ +struct test_driver_data_private { + size_t size; + u8 *data; + bool written; +}; + +/** + * driver_data_test_device - test device to help test driver_data + * + * @dev_idx: unique ID for test device + * @config: this keeps the device's own configuration. Instead of creating + * different triggers for all possible test cases we can think of in + * kernel, we expose a set possible device attributes for tuning the + * driver_data API and we to let you tune them in userspace. We then just + * provide one trigger. + * @test_driver_data: internal private representation of a storage area + * a driver might typically use to stuff firmware / driver_data. + * @misc_dev: we use a misc device under the hood + * @dev: pointer to misc_dev's own struct device + * @driver_data_mutex: for access into the @driver_data, the fake storage + * location for the system data we copy. + * @config_mutex: + * @trigger_mutex: all triggers are mutually exclusive when testing. To help + * enable testing you can create a different device, each device has its + * own set of protections, mimicking real devices. + * list: needed to be part of the reg_test_devs + */ +struct driver_data_test_device { + int dev_idx; + struct test_config config; + struct test_driver_data_private test_driver_data; + struct miscdevice misc_dev; + struct device *dev; + + struct mutex driver_data_mutex; + struct mutex config_mutex; + struct mutex trigger_mutex; + struct list_head list; +}; + +static struct miscdevice *dev_to_misc_dev(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static struct driver_data_test_device * +misc_dev_to_test_dev(struct miscdevice *misc_dev) +{ + return container_of(misc_dev, struct driver_data_test_device, misc_dev); +} + +static struct driver_data_test_device *dev_to_test_dev(struct device *dev) +{ + struct miscdevice *misc_dev; + + misc_dev = dev_to_misc_dev(dev); + + return misc_dev_to_test_dev(misc_dev); +} + +static ssize_t test_fw_misc_read(struct file *f, char __user *buf, + size_t size, loff_t *offset) +{ + struct miscdevice *misc_dev = f->private_data; + struct driver_data_test_device *test_dev = + misc_dev_to_test_dev(misc_dev); + struct test_driver_data_private *test_driver_data = + &test_dev->test_driver_data; + ssize_t ret = 0; + + mutex_lock(&test_dev->driver_data_mutex); + if (test_driver_data->written) + ret = simple_read_from_buffer(buf, size, offset, + test_driver_data->data, + test_driver_data->size); + mutex_unlock(&test_dev->driver_data_mutex); + + return ret; +} + +static const struct file_operations test_fw_fops = { + .owner = THIS_MODULE, + .read = test_fw_misc_read, +}; + +static +void free_test_driver_data(struct test_driver_data_private *test_driver_data) +{ + kfree(test_driver_data->data); + test_driver_data->data = NULL; + test_driver_data->size = 0; + test_driver_data->written = false; +} + +static int test_load_driver_data(struct driver_data_test_device *test_dev, + const struct driver_data *driver_data) +{ + struct test_driver_data_private *test_driver_data = + &test_dev->test_driver_data; + int ret = 0; + + if (!driver_data) + return -ENOENT; + + mutex_lock(&test_dev->driver_data_mutex); + + free_test_driver_data(test_driver_data); + + test_driver_data->data = kzalloc(driver_data->size, GFP_KERNEL); + if (!test_driver_data->data) { + ret = -ENOMEM; + goto out; + } + + memcpy(test_driver_data->data, driver_data->data, driver_data->size); + test_driver_data->size = driver_data->size; + test_driver_data->written = true; + + dev_info(test_dev->dev, "loaded: %zu\n", test_driver_data->size); + +out: + mutex_unlock(&test_dev->driver_data_mutex); + + return ret; +} + +static int sync_found_cb(void *context, const struct driver_data *driver_data) +{ + struct driver_data_test_device *test_dev = context; + int ret; + + ret = test_load_driver_data(test_dev, driver_data); + if (ret) + dev_info(test_dev->dev, + "unable to write driver_data: %d\n", ret); + return ret; +} + +static ssize_t config_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + int len = 0; + + mutex_lock(&test_dev->config_mutex); + + len += snprintf(buf, PAGE_SIZE, + "Custom trigger configuration for: %s\n", + dev_name(dev)); + + if (config->default_name) + len += snprintf(buf+len, PAGE_SIZE, + "default name:\t%s\n", + config->default_name); + else + len += snprintf(buf+len, PAGE_SIZE, + "default name:\tEMTPY\n"); + + if (config->name) + len += snprintf(buf+len, PAGE_SIZE, + "name:\t\t%s\n", config->name); + else + len += snprintf(buf+len, PAGE_SIZE, + "name:\t\tEMPTY\n"); + + len += snprintf(buf+len, PAGE_SIZE, + "type:\t\t%s\n", + config->async ? "async" : "sync"); + len += snprintf(buf+len, PAGE_SIZE, + "optional:\t%s\n", + config->optional ? "true" : "false"); + len += snprintf(buf+len, PAGE_SIZE, + "enable_opt_cb:\t%s\n", + config->enable_opt_cb ? "true" : "false"); + len += snprintf(buf+len, PAGE_SIZE, + "keep:\t\t%s\n", + config->keep ? "true" : "false"); + + mutex_unlock(&test_dev->config_mutex); + + return len; +} +static DEVICE_ATTR_RO(config); + +static int config_load_data(struct driver_data_test_device *test_dev, + const struct driver_data *driver_data) +{ + struct test_config *config = &test_dev->config; + int ret; + + ret = test_load_driver_data(test_dev, driver_data); + if (ret) { + if (!config->optional) + dev_info(test_dev->dev, + "unable to write driver_data\n"); + } + if (config->keep) { + release_driver_data(driver_data); + driver_data = NULL; + } + + return ret; +} + +static int config_req_default(struct driver_data_test_device *test_dev) +{ + struct test_config *config = &test_dev->config; + int ret; + /* + * Note: we don't chain config->optional here, we make this + * fallback file a requirement. It doesn't make much sense to test + * chaining further as the optional callback is implementation + * specific, by testing it once we test it for any possible + * chains. We provide this as an example of what people can do + * and use a default non-optional fallback. + */ + const struct driver_data_req_params req_params = { + DRIVER_DATA_DEFAULT_SYNC(sync_found_cb, test_dev), + }; + + if (config->async) + dev_info(test_dev->dev, + "loading default fallback '%s' using sync request now\n", + config->default_name); + else + dev_info(test_dev->dev, + "loading default fallback '%s'\n", + config->default_name); + + ret = driver_data_request(config->default_name, + &req_params, test_dev->dev); + if (ret) + dev_info(test_dev->dev, + "load of default '%s' failed: %d\n", + config->default_name, ret); + + return ret; +} + +/* + * This is the default sync fallback callback, as a fallback this + * then uses a sync request. + */ +static int config_sync_req_default_cb(void *context) +{ + struct driver_data_test_device *test_dev = context; + int ret; + + ret = config_req_default(test_dev); + + return ret; + + /* Leave all the error checking for the main caller */ +} + +/* + * This is the default config->async fallback callback, as a fallback this + * then uses a sync request. + */ +static void config_async_req_default_cb(void *context) +{ + struct driver_data_test_device *test_dev = context; + + config_req_default(test_dev); + + /* Leave all the error checking for the main caller */ +} + +static int config_sync_req_cb(void *context, + const struct driver_data *driver_data) +{ + struct driver_data_test_device *test_dev = context; + + return config_load_data(test_dev, driver_data); +} + +static int trigger_config_sync(struct driver_data_test_device *test_dev) +{ + struct test_config *config = &test_dev->config; + int ret; + const struct driver_data_req_params req_params_default = { + DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev), + .optional = config->optional, + .keep = config->keep, + }; + const struct driver_data_req_params req_params_opt_cb = { + DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev), + DRIVER_DATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev), + .optional = config->optional, + .keep = config->keep, + }; + const struct driver_data_req_params *req_params; + + if (config->enable_opt_cb) + req_params = &req_params_opt_cb; + else + req_params = &req_params_default; + + ret = driver_data_request(config->name, req_params, test_dev->dev); + if (ret) + dev_err(test_dev->dev, "sync load of '%s' failed: %d\n", + config->name, ret); + + return ret; +} + +static void config_async_req_cb(const struct driver_data *driver_data, + void *context) +{ + struct driver_data_test_device *test_dev = context; + + config_load_data(test_dev, driver_data); +} + +static int trigger_config_async(struct driver_data_test_device *test_dev) +{ + struct test_config *config = &test_dev->config; + int ret; + const struct driver_data_req_params req_params_default = { + DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev), + .sync_reqs.mode = config->async ? + DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC, + .optional = config->optional, + .keep = config->keep, + }; + const struct driver_data_req_params req_params_opt_cb = { + DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev), + DRIVER_DATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev), + .sync_reqs.mode = config->async ? + DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC, + .optional = config->optional, + .keep = config->keep, + }; + const struct driver_data_req_params *req_params; + async_cookie_t async_cookie; + + if (config->enable_opt_cb) + req_params = &req_params_opt_cb; + else + req_params = &req_params_default; + + ret = driver_data_request_async(config->name, req_params, + test_dev->dev, &async_cookie); + if (ret) { + dev_err(test_dev->dev, "async load of '%s' failed: %d\n", + config->name, ret); + goto out; + } + + driver_data_synchronize_request(async_cookie); +out: + return ret; +} + +static ssize_t +trigger_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_driver_data_private *test_driver_data = + &test_dev->test_driver_data; + struct test_config *config = &test_dev->config; + int ret; + + mutex_lock(&test_dev->trigger_mutex); + mutex_lock(&test_dev->config_mutex); + + dev_info(dev, "loading '%s'\n", config->name); + + if (config->async) + ret = trigger_config_async(test_dev); + else + ret = trigger_config_sync(test_dev); + + config->test_result = ret; + + if (ret) + goto out; + + if (test_driver_data->written) { + dev_info(dev, "loaded: %zu\n", test_driver_data->size); + ret = count; + } else { + dev_err(dev, "failed to load firmware\n"); + ret = -ENODEV; + } + +out: + mutex_unlock(&test_dev->config_mutex); + mutex_unlock(&test_dev->trigger_mutex); + + return ret; +} +static DEVICE_ATTR_WO(trigger_config); + +/* + * XXX: move to kstrncpy() once merged. + * + * Users should use kfree_const() when freeing these. + */ +static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp) +{ + *dst = kstrndup(name, count, gfp); + if (!*dst) + return -ENOSPC; + return count; +} + +static void __driver_data_config_free(struct test_config *config) +{ + kfree_const(config->name); + config->name = NULL; + kfree_const(config->default_name); + config->default_name = NULL; +} + +static void driver_data_config_free(struct driver_data_test_device *test_dev) +{ + struct test_config *config = &test_dev->config; + + mutex_lock(&test_dev->config_mutex); + __driver_data_config_free(config); + mutex_unlock(&test_dev->config_mutex); +} + +static int __driver_data_config_init(struct test_config *config) +{ + int ret; + + ret = __kstrncpy(&config->name, TEST_DRIVER_DATA, + strlen(TEST_DRIVER_DATA), GFP_KERNEL); + if (ret < 0) + goto out; + + ret = __kstrncpy(&config->default_name, TEST_DRIVER_DATA, + strlen(TEST_DRIVER_DATA), GFP_KERNEL); + if (ret < 0) + goto out; + + config->async = false; + config->optional = false; + config->keep = false; + config->enable_opt_cb = false; + config->test_result = 0; + + return 0; + +out: + __driver_data_config_free(config); + return ret; +} + +int driver_data_config_init(struct driver_data_test_device *test_dev) +{ + struct test_config *config = &test_dev->config; + int ret; + + mutex_lock(&test_dev->config_mutex); + ret = __driver_data_config_init(config); + mutex_unlock(&test_dev->config_mutex); + + return ret; +} + +static ssize_t config_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + int ret; + + mutex_lock(&test_dev->config_mutex); + kfree_const(config->name); + ret = __kstrncpy(&config->name, buf, count, GFP_KERNEL); + mutex_unlock(&test_dev->config_mutex); + + return ret; +} + +/* + * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE. + */ +static ssize_t config_test_show_str(struct mutex *config_mutex, + char *dst, + char *src) +{ + int len; + + mutex_lock(config_mutex); + len = snprintf(dst, PAGE_SIZE, "%s\n", src); + mutex_unlock(config_mutex); + + return len; +} + +static ssize_t config_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return config_test_show_str(&test_dev->config_mutex, buf, + config->name); +} +static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store); + +static ssize_t config_default_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + int ret; + + mutex_lock(&test_dev->config_mutex); + kfree_const(config->default_name); + ret = __kstrncpy(&config->default_name, buf, count, GFP_KERNEL); + mutex_unlock(&test_dev->config_mutex); + + return ret; +} + +static ssize_t config_default_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return config_test_show_str(&test_dev->config_mutex, buf, + config->default_name); +} +static DEVICE_ATTR(config_default_name, 0644, config_default_name_show, + config_default_name_store); + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + int ret; + + mutex_lock(&test_dev->trigger_mutex); + + mutex_lock(&test_dev->driver_data_mutex); + free_test_driver_data(&test_dev->test_driver_data); + mutex_unlock(&test_dev->driver_data_mutex); + + mutex_lock(&test_dev->config_mutex); + + __driver_data_config_free(config); + + ret = __driver_data_config_init(config); + if (ret < 0) { + ret = -ENOMEM; + dev_err(dev, "could not alloc settings for config trigger: %d\n", + ret); + goto out; + } + + dev_info(dev, "reset\n"); + ret = count; + +out: + mutex_unlock(&test_dev->config_mutex); + mutex_unlock(&test_dev->trigger_mutex); + + return ret; +} +static DEVICE_ATTR_WO(reset); + +/* + * XXX: consider a soluton to generalize drivers to specify their own + * mutex, adding it to dev core after this gets merged. This may not + * be important for once-in-a-while system tuning parameters, but if + * we want to enable fuzz testing, this is really important. + * + * It may make sense to just have a "struct device configuration mutex" + * for these sorts of things, although there is difficulty in that we'd + * need dynamically allocated attributes for that. Its the same reason + * why we ended up not using the provided standard device attribute + * bool, int interfaces. + */ + +static int test_dev_config_update_bool(struct driver_data_test_device *test_dev, + const char *buf, size_t size, + bool *config) +{ + int ret; + + mutex_lock(&test_dev->config_mutex); + if (strtobool(buf, config) < 0) + ret = -EINVAL; + else + ret = size; + mutex_unlock(&test_dev->config_mutex); + + return ret; +} + +static ssize_t +test_dev_config_show_bool(struct driver_data_test_device *test_dev, + char *buf, + bool config) +{ + bool val; + + mutex_lock(&test_dev->config_mutex); + val = config; + mutex_unlock(&test_dev->config_mutex); + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static int test_dev_config_update_int(struct driver_data_test_device *test_dev, + const char *buf, size_t size, + int *config) +{ + int ret; + long new; + + ret = kstrtol(buf, 10, &new); + if (ret) + return ret; + + if (new > INT_MAX || new < INT_MIN) + return -EINVAL; + + mutex_lock(&test_dev->config_mutex); + *(int *)config = new; + mutex_unlock(&test_dev->config_mutex); + + /* Always return full write size even if we didn't consume all */ + return size; +} + +static +ssize_t test_dev_config_show_int(struct driver_data_test_device *test_dev, + char *buf, + int config) +{ + int val; + + mutex_lock(&test_dev->config_mutex); + val = config; + mutex_unlock(&test_dev->config_mutex); + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t config_async_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_update_bool(test_dev, buf, count, + &config->async); +} + +static ssize_t config_async_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_show_bool(test_dev, buf, config->async); +} +static DEVICE_ATTR(config_async, 0644, config_async_show, config_async_store); + +static ssize_t config_optional_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_update_bool(test_dev, buf, count, + &config->optional); +} + +static ssize_t config_optional_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_show_bool(test_dev, buf, config->optional); +} +static DEVICE_ATTR(config_optional, 0644, config_optional_show, + config_optional_store); + +static ssize_t config_keep_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_update_bool(test_dev, buf, count, + &config->keep); +} + +static ssize_t config_keep_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_show_bool(test_dev, buf, config->keep); +} +static DEVICE_ATTR(config_keep, 0644, config_keep_show, config_keep_store); + +static ssize_t config_enable_opt_cb_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_update_bool(test_dev, buf, count, + &config->enable_opt_cb); +} + +static ssize_t config_enable_opt_cb_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_show_bool(test_dev, buf, + config->enable_opt_cb); +} +static DEVICE_ATTR(config_enable_opt_cb, 0644, + config_enable_opt_cb_show, + config_enable_opt_cb_store); + +static ssize_t test_result_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_update_int(test_dev, buf, count, + &config->test_result); +} + +static ssize_t test_result_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data_test_device *test_dev = dev_to_test_dev(dev); + struct test_config *config = &test_dev->config; + + return test_dev_config_show_int(test_dev, buf, config->test_result); +} +static DEVICE_ATTR(test_result, 0644, test_result_show, test_result_store); + +#define TEST_DRIVER_DATA_DEV_ATTR(name) &dev_attr_##name.attr + +static struct attribute *test_dev_attrs[] = { + TEST_DRIVER_DATA_DEV_ATTR(trigger_config), + TEST_DRIVER_DATA_DEV_ATTR(config), + TEST_DRIVER_DATA_DEV_ATTR(reset), + + TEST_DRIVER_DATA_DEV_ATTR(config_name), + TEST_DRIVER_DATA_DEV_ATTR(config_default_name), + TEST_DRIVER_DATA_DEV_ATTR(config_async), + TEST_DRIVER_DATA_DEV_ATTR(config_optional), + TEST_DRIVER_DATA_DEV_ATTR(config_keep), + TEST_DRIVER_DATA_DEV_ATTR(config_enable_opt_cb), + TEST_DRIVER_DATA_DEV_ATTR(test_result), + + NULL, +}; + +ATTRIBUTE_GROUPS(test_dev); + +void free_test_dev_driver_data(struct driver_data_test_device *test_dev) +{ + kfree_const(test_dev->misc_dev.name); + test_dev->misc_dev.name = NULL; + vfree(test_dev); + test_dev = NULL; + driver_data_config_free(test_dev); +} + +void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev) +{ + dev_info(test_dev->dev, "removing interface\n"); + misc_deregister(&test_dev->misc_dev); + kfree(&test_dev->misc_dev.name); + free_test_dev_driver_data(test_dev); +} + +struct driver_data_test_device *alloc_test_dev_driver_data(int idx) +{ + int ret; + struct driver_data_test_device *test_dev; + struct miscdevice *misc_dev; + + test_dev = vzalloc(sizeof(struct driver_data_test_device)); + if (!test_dev) + goto err_out; + + mutex_init(&test_dev->driver_data_mutex); + mutex_init(&test_dev->config_mutex); + mutex_init(&test_dev->trigger_mutex); + + ret = driver_data_config_init(test_dev); + if (ret < 0) + goto err_out_free; + + test_dev->dev_idx = idx; + misc_dev = &test_dev->misc_dev; + + misc_dev->minor = MISC_DYNAMIC_MINOR; + misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx); + if (!misc_dev->name) + goto err_out_free_config; + + misc_dev->fops = &test_fw_fops; + misc_dev->groups = test_dev_groups; + + return test_dev; + +err_out_free_config: + __driver_data_config_free(&test_dev->config); +err_out_free: + kfree(test_dev); +err_out: + return NULL; +} + +static int register_test_dev_driver_data(void) +{ + struct driver_data_test_device *test_dev = NULL; + int ret = -ENODEV; + + mutex_lock(®_dev_mutex); + + /* int should suffice for number of devices, test for wrap */ + if (unlikely(num_test_devs + 1) < 0) { + pr_err("reached limit of number of test devices\n"); + goto out; + } + + test_dev = alloc_test_dev_driver_data(num_test_devs); + if (!test_dev) { + ret = -ENOMEM; + goto out; + } + + ret = misc_register(&test_dev->misc_dev); + if (ret) { + pr_err("could not register misc device: %d\n", ret); + free_test_dev_driver_data(test_dev); + goto out; + } + + test_dev->dev = test_dev->misc_dev.this_device; + list_add_tail(&test_dev->list, ®_test_devs); + dev_info(test_dev->dev, "interface ready\n"); + + num_test_devs++; + +out: + mutex_unlock(®_dev_mutex); + + return ret; +} + +static int __init test_driver_data_init(void) +{ + int ret; + + ret = register_test_dev_driver_data(); + if (ret) + pr_err("Cannot add first test driver_data device\n"); + + return ret; +} +late_initcall(test_driver_data_init); + +static void __exit test_driver_data_exit(void) +{ + struct driver_data_test_device *test_dev, *tmp; + + mutex_lock(®_dev_mutex); + list_for_each_entry_safe(test_dev, tmp, ®_test_devs, list) { + list_del(&test_dev->list); + unregister_test_dev_driver_data(test_dev); + } + mutex_unlock(®_dev_mutex); +} + +module_exit(test_driver_data_exit); + +MODULE_AUTHOR("Luis R. Rodriguez <mcg...@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile index 1894d625af2d..c9bf6c44435f 100644 --- a/tools/testing/selftests/firmware/Makefile +++ b/tools/testing/selftests/firmware/Makefile @@ -3,7 +3,7 @@ # No binaries, but make sure arg-less "make" doesn't trigger "run_tests" all: -TEST_PROGS := fw_filesystem.sh fw_fallback.sh +TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh include ../lib.mk diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config index c8137f70e291..0f1a299f9270 100644 --- a/tools/testing/selftests/firmware/config +++ b/tools/testing/selftests/firmware/config @@ -1 +1,2 @@ CONFIG_TEST_FIRMWARE=y +CONFIG_TEST_DRIVER_DATA=y diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh new file mode 100755 index 000000000000..7884cda3dd16 --- /dev/null +++ b/tools/testing/selftests/firmware/driver_data.sh @@ -0,0 +1,826 @@ +#!/bin/bash +# Copyright (C) 2016 Luis R. Rodriguez <mcg...@kernel.org> +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of copyleft-next (version 0.3.1 or later) as published +# at http://copyleft-next.org/. + +# This performs a series tests against firmware_class to excercise the +# firmware_class driver with focus only on the extensible driver data API. +# +# To make this test self contained, and not pollute your distribution +# firmware install paths, we reset the custom load directory to a +# temporary location. + +set -e + +TEST_NAME="driver_data" +TEST_DRIVER="test_${TEST_NAME}" +TEST_DIR=$(dirname $0) + +# This represents +# +# TEST_ID:TEST_COUNT:ENABLED +# +# TEST_ID: is the test id number +# TEST_COUNT: number of times we should run the test +# ENABLED: 1 if enabled, 0 otherwise +# +# Once these are enabled please leave them as-is. Write your own test, +# we have tons of space. +ALL_TESTS="0001:3:1" +ALL_TESTS="$ALL_TESTS 0002:3:1" +ALL_TESTS="$ALL_TESTS 0003:3:1" +ALL_TESTS="$ALL_TESTS 0004:10:1" +ALL_TESTS="$ALL_TESTS 0005:10:1" +ALL_TESTS="$ALL_TESTS 0006:10:1" +ALL_TESTS="$ALL_TESTS 0007:10:1" +ALL_TESTS="$ALL_TESTS 0008:10:1" +ALL_TESTS="$ALL_TESTS 0009:10:1" +ALL_TESTS="$ALL_TESTS 0010:10:1" + +test_modprobe() +{ + if [ ! -d $DIR ]; then + echo "$0: $DIR not present" >&2 + echo "You must have the following enabled in your kernel:" >&2 + cat $TEST_DIR/config >&2 + exit 1 + fi +} + +function allow_user_defaults() +{ + if [ -z $DIR ]; then + DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/" + fi + + if [ -z $DEFAULT_NUM_TESTS ]; then + DEFAULT_NUM_TESTS=50 + fi + + if [ -z $FW_SYSFSPATH ]; then + FW_SYSFSPATH="/sys/module/firmware_class/parameters/path" + fi + + if [ -z $OLD_FWPATH ]; then + OLD_FWPATH=$(cat $FW_SYSFSPATH) + fi + + if [ -z $FWPATH]; then + FWPATH=$(mktemp -d) + fi + + if [ -z $DEFAULT_DRIVER_DATA ]; then + DEFAULT_DRIVER_DATA="test-driver_data.bin" + fi + + if [ -z $FW ]; then + FW="$FWPATH/$DEFAULT_DRIVER_DATA" + fi + + # Set the kernel search path. + echo -n "$FWPATH" > $FW_SYSFSPATH + + # This is an unlikely real-world firmware content. :) + echo "ABCD0123" >"$FW" + + NAME=$(basename "$FW") +} + +test_reqs() +{ + if ! which diff 2> /dev/null > /dev/null; then + echo "$0: You need diff installed" + exit 1 + fi + + uid=$(id -u) + if [ $uid -ne 0 ]; then + echo $msg must be run as root >&2 + exit 0 + fi +} + +function load_req_mod() +{ + trap "test_modprobe" EXIT + + if [ ! -d $DIR ]; then + modprobe $TEST_DRIVER + fi +} + +test_finish() +{ + echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path + rm -f "$FW" + rmdir "$FWPATH" +} + +errno_name_to_val() +{ + case "$1" in + SUCCESS) + echo 0;; + -EPERM) + echo -1;; + -ENOENT) + echo -2;; + -EINVAL) + echo -22;; + -ERR_ANY) + echo -123456;; + *) + echo invalid;; + esac +} + +errno_val_to_name() + case "$1" in + 0) + echo SUCCESS;; + -1) + echo -EPERM;; + -2) + echo -ENOENT;; + -22) + echo -EINVAL;; + -123456) + echo -ERR_ANY;; + *) + echo invalid;; + esac + +config_set_async() +{ + if ! echo -n 1 >$DIR/config_async ; then + echo "$0: Unable to set to async" >&2 + exit 1 + fi +} + +config_disable_async() +{ + if ! echo -n 0 >$DIR/config_async ; then + echo "$0: Unable to set to sync" >&2 + exit 1 + fi +} + +config_set_optional() +{ + if ! echo -n 1 >$DIR/config_optional ; then + echo "$0: Unable to set to optional" >&2 + exit 1 + fi +} + +config_disable_optional() +{ + if ! echo -n 0 >$DIR/config_optional ; then + echo "$0: Unable to disable optional" >&2 + exit 1 + fi +} + +config_set_keep() +{ + if ! echo -n 1 >$DIR/config_keep; then + echo "$0: Unable to set to keep" >&2 + exit 1 + fi +} + +config_disable_keep() +{ + if ! echo -n 0 >$DIR/config_keep; then + echo "$0: Unable to disable keep option" >&2 + exit 1 + fi +} + +config_enable_opt_cb() +{ + if ! echo -n 1 >$DIR/config_enable_opt_cb; then + echo "$0: Unable to set to optional" >&2 + exit 1 + fi +} + +config_disable_opt_cb() +{ + if ! echo -n 0 >$DIR/config_enable_opt_cb; then + echo "$0: Unable to disable keep option" >&2 + exit 1 + fi +} + +# For special characters use printf directly, +# refer to driver_data_test_0001 +config_set_name() +{ + if ! echo -n $1 >$DIR/config_name; then + echo "$0: Unable to set name" >&2 + exit 1 + fi +} + +config_get_name() +{ + cat $DIR/config_name +} + +# For special characters use printf directly, +# refer to driver_data_test_0001 +config_set_default_name() +{ + if ! echo -n $1 >$DIR/config_default_name; then + echo "$0: Unable to set default_name" >&2 + exit 1 + fi +} + +config_get_default_name() +{ + cat $DIR/config_default_name +} + +config_get_test_result() +{ + cat $DIR/test_result +} + +config_reset() +{ + if ! echo -n "1" >"$DIR"/reset; then + echo "$0: reset shuld have worked" >&2 + exit 1 + fi +} + +config_show_config() +{ + echo "----------------------------------------------------" + cat "$DIR"/config + echo "----------------------------------------------------" +} + +config_trigger() +{ + if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then + echo "$1: FAIL - loading should have worked" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - loading driver_data" +} + +config_trigger_want_fail() +{ + if echo "1" > $DIR/trigger_config 2>/dev/null; then + echo "$1: FAIL - loading was expected to fail" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - loading failed as expected" +} + +config_file_should_match() +{ + FILE=$(config_get_name) + # On this one we expect the file to exist so leave stderr in + if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then + echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - $FILE == /dev/test_driver_data0" +} + +config_file_should_match_default() +{ + FILE=$(config_get_default_name) + # On this one we expect the file to exist so leave stderr in + if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then + echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - $FILE == /dev/test_driver_data0" +} + +config_file_should_not_match() +{ + FILE=$(config_get_name) + # File may not exist, so skip those error messages as well + if $(diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null) 2> /dev/null ; then + echo "$1: FAIL - file $FILE was not expected to match /dev/null" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - $FILE != /dev/test_driver_data0" +} + +config_default_file_should_match() +{ + FILE=$(config_get_default_name) + diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null + if ! $? ; then + echo "$1: FAIL - file $FILE expected to match /dev/test_driver_data0" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! [file integrity matches]" +} + +config_default_file_should_not_match() +{ + FILE=$(config_get_default_name) + diff -q FWPATH/$FILE /dev/test_driver_data0 2> /dev/null + if $? 2> /dev/null ; then + echo "$1: FAIL - file $FILE was not expected to match test_driver_data0" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK!" +} + +config_expect_result() +{ + RC=$(config_get_test_result) + RC_NAME=$(errno_val_to_name $RC) + + ERRNO_NAME=$2 + ERRNO=$(errno_name_to_val $ERRNO_NAME) + + if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then + if [[ $RC -ge 0 ]]; then + echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2 + config_show_config >&2 + exit 1 + fi + elif [[ $RC != $ERRNO ]]; then + echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2 + config_show_config >&2 + exit 1 + fi + echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME" +} + +driver_data_set_sync_defaults() +{ + config_reset +} + +driver_data_set_async_defaults() +{ + config_reset + config_set_async +} + +driver_data_test_0001s() +{ + NAME='\000' + + driver_data_set_sync_defaults + config_set_name $NAME + printf '\000' >"$DIR"/config_name + config_trigger_want_fail ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} -EINVAL +} + +driver_data_test_0001a() +{ + NAME='\000' + + driver_data_set_async_defaults + printf '\000' >"$DIR"/config_name + config_trigger_want_fail ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} -EINVAL +} + +driver_data_test_0001() +{ + driver_data_test_0001s + driver_data_test_0001a +} + +driver_data_test_0002s() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_sync_defaults + config_set_name ${FUNCNAME[0]} + config_trigger_want_fail ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} -ENOENT +} + +driver_data_test_0002a() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_async_defaults + config_set_name $NAME + config_trigger_want_fail ${FUNCNAME[0]} + # This may seem odd to expect success on a bogus + # file but remember this is an async call, the actual + # error handling is managed by the async callbacks. + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0002() +{ + driver_data_test_0002s + driver_data_test_0002a +} + +driver_data_test_0003() +{ + config_reset + config_file_should_not_match ${FUNCNAME[0]} +} + +driver_data_test_0004s() +{ + driver_data_set_sync_defaults + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0004a() +{ + driver_data_set_async_defaults + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0004() +{ + driver_data_test_0004s + driver_data_test_0004a +} + +driver_data_test_0005s() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_sync_defaults + config_set_optional + config_set_name $NAME + config_trigger_want_fail ${FUNCNAME[0]} + # We do this to ensure the default backup callback hasn't + # been called yet + config_file_should_not_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0005a() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_async_defaults + config_set_optional + config_set_name $NAME + config_trigger_want_fail ${FUNCNAME[0]} + # We do this to ensure the default backup callback hasn't + # been called yet + config_file_should_not_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0005() +{ + driver_data_test_0005s + driver_data_test_0005a +} + +driver_data_test_0006s() +{ + driver_data_set_sync_defaults + config_set_optional + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0006a() +{ + driver_data_set_async_defaults + config_set_optional + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0006() +{ + driver_data_test_0006s + driver_data_test_0006a +} + +driver_data_test_0007s() +{ + driver_data_set_sync_defaults + config_set_keep + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0007a() +{ + driver_data_set_async_defaults + config_set_keep + config_trigger ${FUNCNAME[0]} + config_file_should_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0007() +{ + driver_data_test_0007s + driver_data_test_0007a +} + +driver_data_test_0008s() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_sync_defaults + config_set_name $NAME + config_set_optional + config_enable_opt_cb + config_trigger ${FUNCNAME[0]} + config_file_should_match_default ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0008a() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_async_defaults + config_set_name $NAME + config_set_optional + config_enable_opt_cb + config_trigger ${FUNCNAME[0]} + config_file_should_match_default ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0008() +{ + driver_data_test_0008s + driver_data_test_0008a +} + +driver_data_test_0009s() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_sync_defaults + config_set_name $NAME + config_set_keep + config_set_optional + config_enable_opt_cb + config_trigger ${FUNCNAME[0]} + config_file_should_match_default ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0009a() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_async_defaults + config_set_name $NAME + config_set_keep + config_set_optional + config_enable_opt_cb + config_trigger ${FUNCNAME[0]} + config_file_should_match_default ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0009() +{ + driver_data_test_0009s + driver_data_test_0009a +} + +driver_data_test_0010s() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_sync_defaults + config_set_name $NAME + config_set_default_name $NAME + config_set_keep + config_set_optional + config_enable_opt_cb + config_trigger_want_fail ${FUNCNAME[0]} + config_file_should_not_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} -ENOENT +} + +driver_data_test_0010a() +{ + NAME="nope-$DEFAULT_DRIVER_DATA" + + driver_data_set_async_defaults + config_set_name $NAME + config_set_default_name $NAME + config_set_keep + config_set_optional + config_enable_opt_cb + config_trigger_want_fail ${FUNCNAME[0]} + config_file_should_not_match ${FUNCNAME[0]} + config_expect_result ${FUNCNAME[0]} SUCCESS +} + +driver_data_test_0010() +{ + driver_data_test_0010s + driver_data_test_0010a +} + +list_tests() +{ + echo "Test ID list:" + echo + echo "TEST_ID x NUM_TEST" + echo "TEST_ID: Test ID" + echo "NUM_TESTS: Number of recommended times to run the test" + echo + echo "0001 x $(get_test_count 0001) - Empty string should be ignored" + echo "0002 x $(get_test_count 0002) - Files that do not exist should be ignored" + echo "0003 x $(get_test_count 0003) - Verify test_driver_data0 has nothing loaded upon reset" + echo "0004 x $(get_test_count 0004) - Simple sync and async loader" + echo "0005 x $(get_test_count 0005) - Verify optional loading is not fatal" + echo "0006 x $(get_test_count 0006) - Verify optional loading enables loading" + echo "0007 x $(get_test_count 0007) - Verify keep works" + echo "0008 x $(get_test_count 0008) - Verify optional callback works" + echo "0009 x $(get_test_count 0009) - Verify optional callback works, keep" + echo "0010 x $(get_test_count 0010) - Verify when fallback file is not present" + echo "0011 x $(get_test_count 0011) - Verify requesting module after driver data works" +} + +test_reqs + +usage() +{ + NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .) + let NUM_TESTS=$NUM_TESTS+1 + MAX_TEST=$(printf "%04d\n" $NUM_TESTS) + echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |" + echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>" + echo " [ all ] [ -h | --help ] [ -l ]" + echo "" + echo "Valid tests: 0001-$MAX_TEST" + echo "" + echo " all Runs all tests (default)" + echo " -t Run test ID the number amount of times is recommended" + echo " -w Watch test ID run until it runs into an error" + echo " -c Run test ID once" + echo " -s Run test ID x test-count number of times" + echo " -l List all test ID list" + echo " -h|--help Help" + echo + echo "If an error every occurs execution will immediately terminate." + echo "If you are adding a new test try using -w <test-ID> first to" + echo "make sure the test passes a series of tests." + echo + echo Example uses: + echo + echo "$TEST_NAME.sh -- executes all tests" + echo "$TEST_NAME.sh -t 0008 -- Executes test ID 0008 number of times is recomended" + echo "$TEST_NAME.sh -w 0008 -- Watch test ID 0008 run until an error occurs" + echo "$TEST_NAME.sh -s 0008 -- Run test ID 0008 once" + echo "$TEST_NAME.sh -c 0008 3 -- Run test ID 0008 three times" + echo + list_tests + exit 1 +} + +function test_num() +{ + re='^[0-9]+$' + if ! [[ $1 =~ $re ]]; then + usage + fi +} + +function get_test_count() +{ + test_num $1 + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') + LAST_TWO=${TEST_DATA#*:*} + echo ${LAST_TWO%:*} +} + +function get_test_enabled() +{ + test_num $1 + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') + echo ${TEST_DATA#*:*:} +} + +function run_all_tests() +{ + for i in $ALL_TESTS ; do + TEST_ID=${i%:*:*} + ENABLED=$(get_test_enabled $TEST_ID) + TEST_COUNT=$(get_test_count $TEST_ID) + if [[ $ENABLED -eq "1" ]]; then + test_case $TEST_ID $TEST_COUNT + fi + done +} + +function watch_log() +{ + if [ $# -ne 3 ]; then + clear + fi + date + echo "Running test: $2 - run #$1" +} + +function watch_case() +{ + i=0 + while [ 1 ]; do + + if [ $# -eq 1 ]; then + test_num $1 + watch_log $i ${TEST_NAME}_test_$1 + ${TEST_NAME}_test_$1 + else + watch_log $i all + run_all_tests + fi + let i=$i+1 + done +} + +function test_case() +{ + NUM_TESTS=$DEFAULT_NUM_TESTS + if [ $# -eq 2 ]; then + NUM_TESTS=$2 + fi + + i=0 + while [ $i -lt $NUM_TESTS ]; do + test_num $1 + watch_log $i ${TEST_NAME}_test_$1 noclear + RUN_TEST=${TEST_NAME}_test_$1 + $RUN_TEST + let i=$i+1 + done +} + +function parse_args() +{ + if [ $# -eq 0 ]; then + run_all_tests + else + if [[ "$1" = "all" ]]; then + run_all_tests + elif [[ "$1" = "-w" ]]; then + shift + watch_case $@ + elif [[ "$1" = "-t" ]]; then + shift + test_num $1 + test_case $1 $(get_test_count $1) + elif [[ "$1" = "-c" ]]; then + shift + test_num $1 + test_num $2 + test_case $1 $2 + elif [[ "$1" = "-s" ]]; then + shift + test_case $1 1 + elif [[ "$1" = "-l" ]]; then + list_tests + elif [[ "$1" = "-h" || "$1" = "--help" ]]; then + usage + else + usage + fi + fi +} + +test_reqs +allow_user_defaults +load_req_mod + +trap "test_finish" EXIT + +parse_args $@ + +exit 0 -- 2.11.0