diff options
Diffstat (limited to 'test-common.c')
-rw-r--r-- | test-common.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/test-common.c b/test-common.c new file mode 100644 index 0000000..1dab0ce --- /dev/null +++ b/test-common.c @@ -0,0 +1,385 @@ +/**************************************************************************** + * Common test functions for ethtool + * Copyright 2011 Solarflare Communications Inc. + * + * Partly derived from kernel <linux/list.h>. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#include <assert.h> +#include <errno.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <sys/fcntl.h> +#include <unistd.h> +#define TEST_NO_WRAPPERS +#include "internal.h" + +/* List utilities */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +static void init_list_head(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static void list_add(struct list_head *new, struct list_head *head) +{ + head->next->prev = new; + new->next = head->next; + new->prev = head; + head->next = new; +} + +static void list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; + entry->next = NULL; + entry->prev = NULL; +} + +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/* Free memory at end of test */ + +static struct list_head malloc_list = LIST_HEAD_INIT(malloc_list); + +void *test_malloc(size_t size) +{ + struct list_head *block = malloc(sizeof(*block) + size); + + if (!block) + return NULL; + list_add(block, &malloc_list); + return block + 1; +} + +void *test_calloc(size_t nmemb, size_t size) +{ + void *ptr = test_malloc(nmemb * size); + + if (ptr) + memset(ptr, 0, nmemb * size); + return ptr; +} + +char *test_strdup(const char *s) +{ + size_t size = strlen(s) + 1; + char *dup = test_malloc(size); + + if (dup) + memcpy(dup, s, size); + return dup; +} + +void test_free(void *ptr) +{ + struct list_head *block; + + if (!ptr) + return; + block = (struct list_head *)ptr - 1; + list_del(block); + free(block); +} + +void *test_realloc(void *ptr, size_t size) +{ + struct list_head *block = NULL; + + if (ptr) { + block = (struct list_head *)ptr - 1; + list_del(block); + } + block = realloc(block, sizeof(*block) + size); + if (!block) + return NULL; + list_add(block, &malloc_list); + return block + 1; +} + +static void test_free_all(void) +{ + struct list_head *block, *next; + + list_for_each_safe(block, next, &malloc_list) + free(block); + init_list_head(&malloc_list); +} + +/* Close files at end of test */ + +struct file_node { + struct list_head link; + FILE *fh; + int fd; +}; + +static struct list_head file_list = LIST_HEAD_INIT(file_list); + +int test_open(const char *pathname, int flag, ...) +{ + struct file_node *node; + mode_t mode; + + if (flag & O_CREAT) { + va_list ap; + va_start(ap, flag); + mode = va_arg(ap, mode_t); + va_end(ap); + } else { + mode = 0; + } + + node = malloc(sizeof(*node)); + if (!node) + return -1; + + node->fd = open(pathname, flag, mode); + if (node->fd < 0) { + free(node); + return -1; + } + + node->fh = NULL; + list_add(&node->link, &file_list); + return node->fd; +} + +int test_socket(int domain, int type, int protocol) +{ + struct file_node *node; + + node = malloc(sizeof(*node)); + if (!node) + return -1; + + node->fd = socket(domain, type, protocol); + if (node->fd < 0) { + free(node); + return -1; + } + + node->fh = NULL; + list_add(&node->link, &file_list); + return node->fd; +} + +int test_close(int fd) +{ + struct list_head *head, *next; + + if (fd >= 0) { + list_for_each_safe(head, next, &file_list) { + if (((struct file_node *)head)->fd == fd) { + list_del(head); + free(head); + break; + } + } + } + + return close(fd); +} + +FILE *test_fopen(const char *path, const char *mode) +{ + struct file_node *node; + + node = malloc(sizeof(*node)); + if (!node) + return NULL; + + node->fh = fopen(path, mode); + if (!node->fh) { + free(node); + return NULL; + } + + node->fd = -1; + list_add(&node->link, &file_list); + return node->fh; +} + +int test_fclose(FILE *fh) +{ + struct list_head *head, *next; + + assert(fh); + + list_for_each_safe(head, next, &file_list) { + if (((struct file_node *)head)->fh == fh) { + list_del(head); + free(head); + break; + } + } + + return fclose(fh); +} + +static void test_close_all(void) +{ + struct list_head *head, *next; + struct file_node *node; + + list_for_each_safe(head, next, &file_list) { + node = (struct file_node *)head; + if (node->fh) + fclose(node->fh); + else + close(node->fd); + free(node); + } + init_list_head(&file_list); +} + +/* Wrap test main function */ + +static jmp_buf test_return; +static FILE *orig_stderr; + +void test_exit(int rc) +{ + longjmp(test_return, rc + 1); +} + +int test_ioctl(const struct cmd_expect *expect, void *cmd) +{ + int rc; + + if (!expect->cmd || *(u32 *)cmd != *(const u32 *)expect->cmd) { + /* We have no idea how long this command structure is */ + fprintf(orig_stderr, "Unexpected ioctl: cmd=%#10x\n", + *(u32 *)cmd); + return TEST_IOCTL_MISMATCH; + } + + if (memcmp(cmd, expect->cmd, expect->cmd_len)) { + fprintf(orig_stderr, "Expected ioctl structure:\n"); + dump_hex(orig_stderr, expect->cmd, expect->cmd_len, 0); + fprintf(orig_stderr, "Actual ioctl structure:\n"); + dump_hex(orig_stderr, cmd, expect->cmd_len, 0); + return TEST_IOCTL_MISMATCH; + } + + if (expect->resp) + memcpy(cmd, expect->resp, expect->resp_len); + rc = expect->rc; + + /* Convert kernel return code according to libc convention */ + if (rc >= 0) { + return rc; + } else { + errno = -rc; + return -1; + } +} + +int test_cmdline(const char *args) +{ + int volatile orig_stdout_fd = -1; + int volatile orig_stderr_fd = -1; + char **volatile argv; + int volatile argc; + int dev_null = -1; + const char *arg; + size_t len; + int rc, i; + + /* Convert line to argv */ + argc = 1; + arg = args; + for (;;) { + len = strcspn(arg, " "); + if (len == 0) + break; + argc++; + if (arg[len] == 0) + break; + arg += len + 1; + } + argv = test_calloc(argc + 1, sizeof(argv[0])); + argv[0] = test_strdup("ethtool"); + arg = args; + for (i = 1; i < argc; i++) { + len = strcspn(arg, " "); + argv[i] = test_malloc(len + 1); + memcpy(argv[i], arg, len); + argv[i][len] = 0; + arg += len + 1; + } + + dev_null = open("/dev/null", O_RDWR); + if (dev_null < 0) { + perror("open /dev/null"); + rc = -1; + goto out; + } + + fflush(NULL); + dup2(dev_null, STDIN_FILENO); + if (getenv("TEST_TEST_VERBOSE")) { + orig_stderr = stderr; + } else { + orig_stdout_fd = dup(STDOUT_FILENO); + if (orig_stdout_fd < 0) { + perror("dup stdout"); + rc = -1; + goto out; + } + dup2(dev_null, STDOUT_FILENO); + orig_stderr_fd = dup(STDERR_FILENO); + if (orig_stderr_fd < 0) { + perror("dup stderr"); + rc = -1; + goto out; + } + orig_stderr = fdopen(orig_stderr_fd, "w"); + if (orig_stderr == NULL) { + perror("fdopen orig_stderr_fd"); + rc = -1; + goto out; + } + dup2(dev_null, STDERR_FILENO); + } + + rc = setjmp(test_return); + rc = rc ? rc - 1 : test_main(argc, argv); + +out: + fflush(NULL); + if (orig_stderr_fd >= 0) { + dup2(orig_stderr_fd, STDERR_FILENO); + if (orig_stderr) + fclose(orig_stderr); + else + close(orig_stderr_fd); + } + orig_stderr = NULL; + if (orig_stdout_fd >= 0) { + dup2(orig_stdout_fd, STDOUT_FILENO); + close(orig_stdout_fd); + } + if (dev_null >= 0) + close(dev_null); + + test_free_all(); + test_close_all(); + return rc; +} |