summaryrefslogtreecommitdiffstats
path: root/test-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'test-common.c')
-rw-r--r--test-common.c385
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;
+}