summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/usbg-io-wrappers.c170
-rw-r--r--tests/usbg-test.c337
-rw-r--r--tests/usbg-test.h116
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();