diff options
-rw-r--r-- | tests/usbg-io-wrappers.c | 170 | ||||
-rw-r--r-- | tests/usbg-test.c | 337 | ||||
-rw-r--r-- | tests/usbg-test.h | 116 |
3 files changed, 623 insertions, 0 deletions
diff --git a/tests/usbg-io-wrappers.c b/tests/usbg-io-wrappers.c new file mode 100644 index 0000000..e20f149 --- /dev/null +++ b/tests/usbg-io-wrappers.c @@ -0,0 +1,170 @@ +#include <dirent.h> +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <cmocka.h> + +/** + * @brief Simulates opening file + * @details Checks if path is equal expected value and returns given pointer + * from cmocka queue + */ +FILE *fopen(const char *path, const char *mode) +{ + check_expected(path); + return mock_ptr_type(FILE *); +} + +/** + * @brief Simulates closing file + * @details Does absolutely nothing, always acts as successfull close + */ +int fclose(FILE *fp) +{ + check_expected(fp); + return mock_type(int); +} + +/** + * @brief Simulates reading file + * @details Does not read any file, instead returns value from cmocka queue + * @return value specified by caller previously + */ +char *fgets(char *s, int size, FILE *stream) +{ + check_expected(stream); + strncpy(s, mock_ptr_type(char *), size); + return s; +} + +/** + * @brief Simulates opening directory + * @details Does not open any dir, instead returns user-specified value + * @return value specified by caller previously + */ +DIR *opendir(const char *name) +{ + check_expected(name); + return mock_ptr_type(DIR *); +} + +/** + * @brief Simulates closing directory + * @details Does nothing and ends successfully. + */ +int closedir(DIR *dirp) +{ + check_expected(dirp); + return mock_type(int); +} + +/** + * @brief Simulates scanning directory + * @details Checks if dirp has expected value. Then consecutive values from + * cmocka queue are proceed. First value must be integer and indicates number + * of directory entries which should be returned. Next number of values indicate + * names of directory entries. + */ +int scandir(const char *dirp, struct dirent ***namelist, + int (*filter)(const struct dirent *), + int (*compar)(const struct dirent **, const struct dirent **)) +{ + int count; + int i, j = 0; + char *name; + struct dirent **entries; + struct dirent *entry; + int tmp, expected; + + check_expected(dirp); + count = mock_type(int); + + if (count > 0) + entries = calloc(count, sizeof(*entries)); + else + entries = NULL; + + for (i = 0; i < count; i++) { + name = mock_ptr_type(char *); + entry = malloc(sizeof(*entry)); + if (strlen(name) >= NAME_MAX) + fail(); + + strcpy(entry->d_name, name); + entry->d_type = mock_type(unsigned char); + + expected = mock_type(int); + if (filter) { + tmp = filter(entry); + assert_int_equal(tmp, expected); + if (tmp) + entries[j++] = entry; + else + free(entry); + } + } + + if (compar) + qsort(entries, count, sizeof(*entries), + (int (*)(const void *,const void *))compar); + + *namelist = entries; + return j; +} + +/** + * @brief Simultes readlink, with user-specified behavior + * @datails Check if path and bufsiz equal expedted values and + * write to buf string given by cmocka + */ +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + char *res; + int reslen; + + check_expected(path); + check_expected(bufsiz); + res = mock_ptr_type(char *); + reslen = strlen(res); + if (bufsiz <= reslen) + fail(); + + strcpy(buf, res); + + return reslen; +} + +/** + * @brief Simulates puts, with user-specified behavior + * @details Check if user is trying to write expected data + * @return value received from cmocka queue + */ +int fputs(const char *s, FILE *stream) +{ + /* Cmocka (or anything else) may want to print some errors. + * Especially when running fputs itself */ + if (stream == stderr) { + printf("%s", s); + return 0; + } + + check_expected(stream); + check_expected(s); + return mock_type(int); +} + +/** + * @brief Does nothing. + */ +int fflush(FILE *stream) +{ + return 0; +} + +int ferror(FILE *stream) +{ + return 0; +} diff --git a/tests/usbg-test.c b/tests/usbg-test.c new file mode 100644 index 0000000..5ac5f05 --- /dev/null +++ b/tests/usbg-test.c @@ -0,0 +1,337 @@ +#include <usbg/usbg.h> +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include <cmocka.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <limits.h> + +#include "usbg-test.h" + +static struct simple_stack{ + void *ptr; + struct simple_stack *next; +} *cleanup_top = NULL; + +void free_later(void *ptr) +{ + struct simple_stack *t; + + t = malloc(sizeof(*t)); + t->ptr = ptr; + t->next = cleanup_top; + cleanup_top = t; +} + +void cleanup_stack() +{ + struct simple_stack *t; + + while (cleanup_top) { + free(cleanup_top->ptr); + t = cleanup_top->next; + free(cleanup_top); + cleanup_top = t; + } +} + + +/* Represent last file/dir opened, next should have bigger numbers.*/ +static int file_id = 0; +static int dir_id = 0; + +#define PUSH_FILE(file, content) do {\ + file_id++;\ + expect_string(fopen, path, file);\ + will_return(fopen, file_id);\ + expect_value(fgets, stream, file_id);\ + will_return(fgets, content);\ + expect_value(fclose, fp, file_id);\ + will_return(fclose, 0);\ +} while(0) + +#define PUSH_FILE_ALWAYS(dflt) do {\ + expect_any_count(fopen, path, -1);\ + will_return_always(fopen, 1);\ + expect_any_count(fgets, stream, 1);\ + will_return_always(fgets, dflt);\ + expect_any_count(fclose, fp, 1);\ + will_return_always(fclose, 0);\ +} while(0) + +#define PUSH_EMPTY_DIR(p) do {\ + expect_string(scandir, dirp, p);\ + will_return(scandir, 0);\ +} while(0) + +#define EXPECT_OPENDIR(n) do {\ + dir_id++;\ + expect_string(opendir, name, n);\ + will_return(opendir, dir_id);\ + expect_value(closedir, dirp, dir_id);\ + will_return(closedir, 0);\ +} while(0) + +#define PUSH_DIR(p, c) do {\ + expect_string(scandir, dirp, p);\ + will_return(scandir, c);\ +} while(0) + +#define PUSH_DIR_ENTRY(name, type) do {\ + will_return(scandir, name);\ + will_return(scandir, type);\ + will_return(scandir, 1);\ +} while(0) + +#define PUSH_LINK(p, c, len) do {\ + expect_string(readlink, path, p);\ + expect_in_range(readlink, bufsiz, len, INT_MAX);\ + will_return(readlink, c);\ +} while(0) + +/** + * @brief Compare test gadgets' names + */ +static int test_gadget_cmp(struct test_gadget *a, struct test_gadget *b) +{ + return strcoll(a->name, b->name); +} + +/** + * @brief Compare test functions' names + */ +static int test_function_cmp(struct test_function *a, struct test_function *b) +{ + return strcoll(a->name, b->name); +} + +/** + * @brief Compare test configs' names + */ +static int test_config_cmp(struct test_config *a, struct test_config *b) +{ + return strcoll(a->name, b->name); +} + +void prepare_config(struct test_config *c, char *path) +{ + int tmp; + int count = 0; + struct test_function *b; + + tmp = asprintf(&c->name, "%s.%d", + c->label, c->id); + if (tmp < 0) + fail(); + free_later(c->name); + + c->path = path; + + for (b = c->bindings; b->instance; b++) + count++; + + qsort(c->bindings, count, sizeof(*c->bindings), + (int (*)(const void *, const void *))test_function_cmp); + +} + +void prepare_function(struct test_function *f, char *path) +{ + const char *func_type; + int tmp; + + func_type = usbg_get_function_type_str(f->type); + if (func_type == NULL) + fail(); + + tmp = asprintf(&f->name, "%s.%s", + func_type, f->instance); + if (tmp < 0) + fail(); + free_later(f->name); + + f->path = path; +} + +void prepare_gadget(struct test_state *state, struct test_gadget *g) +{ + struct test_config *c; + struct test_function *f; + struct test_binding *b; + char *path; + int tmp; + int count; + + tmp = asprintf(&g->path, "%s/usb_gadget", state->path); + if (tmp < 0) + fail(); + + free_later(g->path); + + tmp = asprintf(&path, "%s/%s/functions", + g->path, g->name); + if (tmp < 0) + fail(); + free_later(path); + + count = 0; + for (f = g->functions; f->instance; f++) { + prepare_function(f, path); + count++; + } + + /* Path needs to be known somehow when list is empty */ + f->path = path; + + qsort(g->functions, count, sizeof(*g->functions), + (int (*)(const void *, const void *))test_function_cmp); + + tmp = asprintf(&path, "%s/%s/configs", + g->path, g->name); + if (tmp < 0) + fail(); + free_later(path); + + count = 0; + for (c = g->configs; c->label; c++) { + prepare_config(c, path); + count++; + } + + /* Path needs to be known somehow when list is empty */ + c->path = path; + + qsort(g->configs, count, sizeof(*g->configs), + (int (*)(const void *, const void *))test_config_cmp); + +} + +void prepare_state(struct test_state *state) +{ + struct test_gadget *g; + int count = 0; + + for (g = state->gadgets; g->name; g++) { + prepare_gadget(state, g); + count++; + } + + qsort(state->gadgets, count, sizeof(*state->gadgets), + (int (*)(const void *, const void *))test_gadget_cmp); + +} + +/* Simulation of configfs for init */ + +static void push_binding(struct test_config *conf, struct test_function *binding) +{ + int tmp; + char *s_path; + char *d_path; + + tmp = asprintf(&s_path, "%s/%s/%s", conf->path, conf->name, binding->name); + if (tmp < 0) + fail(); + free_later(s_path); + + tmp = asprintf(&d_path, "%s/%s", binding->path, binding->name); + if (tmp < 0) + fail(); + free_later(d_path); + + PUSH_LINK(s_path, d_path, USBG_MAX_PATH_LENGTH - 1); +} + +static void push_config(struct test_config *c) +{ + struct test_function *b; + int count = 0; + char *func_name; + int tmp; + char *path; + + tmp = asprintf(&path, "%s/%s", c->path, c->name); + if (tmp < 0) + fail(); + free_later(path); + + for (b = c->bindings; b->instance; b++) + count++; + + PUSH_DIR(path, count); + for (b = c->bindings; b->instance; b++) { + PUSH_DIR_ENTRY(b->name, DT_LNK); + push_binding(c, b); + } +} + +static void push_gadget(struct test_gadget *g) +{ + int count; + struct test_config *c; + struct test_function *f; + int tmp; + char *path; + + tmp = asprintf(&path, "%s/%s/UDC", g->path, g->name); + if (tmp < 0) + fail(); + free_later(path); + PUSH_FILE(path, g->udc); + + count = 0; + for (f = g->functions; f->instance; f++) + count++; + + PUSH_DIR(f->path, count); + for (f = g->functions; f->instance; f++) + PUSH_DIR_ENTRY(f->name, DT_DIR); + + count = 0; + for (c = g->configs; c->label; c++) + count++; + + PUSH_DIR(c->path, count); + for (c = g->configs; c->label; c++) + PUSH_DIR_ENTRY(c->name, DT_DIR); + + for (c = g->configs; c->label; c++) + push_config(c); +} + +void push_init(struct test_state *state) +{ + char **udc; + char *path; + struct test_gadget *g; + int count = 0; + int tmp; + + tmp = asprintf(&path, "%s/usb_gadget", state->path); + if (tmp < 0) + fail(); + free_later(path); + + EXPECT_OPENDIR(path); + + for (udc = state->udcs; *udc; udc++) + count++; + + PUSH_DIR("/sys/class/udc", count); + for (udc = state->udcs; *udc; udc++) + PUSH_DIR_ENTRY(*udc, DT_REG); + + count = 0; + for (g = state->gadgets; g->name; g++) + count++; + + PUSH_DIR(path, count); + for (g = state->gadgets; g->name; g++) { + PUSH_DIR_ENTRY(g->name, DT_DIR); + } + + for (g = state->gadgets; g->name; g++) + push_gadget(g); +} diff --git a/tests/usbg-test.h b/tests/usbg-test.h new file mode 100644 index 0000000..27d408e --- /dev/null +++ b/tests/usbg-test.h @@ -0,0 +1,116 @@ +#include <usbg/usbg.h> +#include <libconfig.h> +#include <sys/queue.h> + +/* Simple structures for defining gadgets. All arrays should be null-terminated.*/ + +struct test_function +{ + usbg_function_type type; + char *instance; + + char *path; + char *name; +}; + +struct test_config +{ + char *label; + int id; + struct test_function *bindings; + + char *path; + char *name; +}; + +struct test_gadget +{ + char *name; + char *udc; + struct test_config *configs; + struct test_function *functions; + + char *path; +}; + +struct test_state +{ + char *path; + struct test_gadget *gadgets; + char **udcs; +}; + +#define TEST_FUNCTION_LIST_END { \ + .instance = NULL, \ + } + +#define TEST_CONFIG_LIST_END { \ + .label = NULL, \ + .bindings = NULL, \ + } + +#define TEST_GADGET_LIST_END { \ + .name = NULL, \ + .udc = NULL, \ + .configs = NULL, \ + .functions = NULL, \ + } + +/** + * @brief Prepare given state for using in tests + * @details Generate full pathes to state elements and sort state's content. + * Must be called before pasing state to push_* and pull_* functions. + * @param[in] state Pointer to state which should be filled out + */ +void prepare_state(struct test_state *state); + +/** + * @brief Prepare given config for using in tests + * @details Generate required pathes for given config and sort content + * (i.e. binding list) + * @param[in] c Config to be filled out + * @param[in] path Path to configs directory + */ +void prepare_config(struct test_config *c, char *path); + +/** + * @brief Prepare given function for using in tests + * @details Generate required pathes for given function + * @param[in] f Function to be filled out + * @param[in] path Path to functions directory + */ +void prepare_function(struct test_function *f, char *path); + +/** + * @brief Prepare given gadget for using in tests + * @details Generate required paths for given gadget and sort it's content + * (i.e. functions list and config list) + * @param[in] state Pointer to gadget's parent state + * @param[in] g Gadget to be filled out + */ +void prepare_gadget(struct test_state *state, struct test_gadget *g); + +/** + * @brief Prepare fake filesystem to init usbg with given test state + * @details Use wrapped i/o functions to simulate configfs state for usbg. + * Calling usbg_init without preparation and with mocked i/o functions + * may fail. + * @param[in] state Fake state of configfs defined in test + */ +void push_init(struct test_state *state); + +/** + * @brief Store given pointer on cleanup stack + * @details All stacked pointers will be freed by calling cleanup_queue. + * This can be used to manage memory needed for single test casees. + */ +void free_later(void *ptr); + +/** + * @brief Cleans up memory no longer needed + * @details Frees all pointer stored on cleanup stack by calling free_later + * @warning Calling this function before end of single test usually make test state + * unusable. Use it only when you no longer need allocated data (at the end of + * test case, in most cases) + */ +void cleanup_stack(); |