[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1338597331.2770.14.camel@bwh-desktop.uk.solarflarecom.com>
Date: Sat, 2 Jun 2012 01:35:31 +0100
From: Ben Hutchings <bhutchings@...arflare.com>
To: <netdev@...r.kernel.org>
CC: Michał Mirosław <mirq-linux@...e.qmqm.pl>,
Mahesh Bandewar <maheshb@...gle.com>
Subject: [PATCH ethtool 1/7] Run tests in-process
Wrap main(), exit(), and resource management so that ethtool commands
can be tested without starting a new process and without leaking.
This will allow deeper teesting that covers ioctl requests and
responses.
Signed-off-by: Ben Hutchings <bhutchings@...arflare.com>
---
.gitignore | 1 -
Makefile.am | 6 +-
ethtool.c | 9 +-
internal.h | 42 ++++++++
test-cmdline.c | 7 ++
test-common.c | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++------
6 files changed, 332 insertions(+), 44 deletions(-)
diff --git a/.gitignore b/.gitignore
index e10c4ef..62bf520 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@ ethtool.spec
ethtool.8
ethtool
test-cmdline
-test-one-cmdline
stamp-h1
config.*
aclocal.m4
diff --git a/Makefile.am b/Makefile.am
index 789cd9a..d1aec43 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -12,11 +12,9 @@ ethtool_SOURCES = ethtool.c ethtool-copy.h internal.h net_tstamp-copy.h \
rxclass.c sfpid.c
TESTS = test-cmdline
-check_PROGRAMS = test-cmdline test-one-cmdline
-test_cmdline_SOURCES = test-cmdline.c test-common.c
+check_PROGRAMS = test-cmdline
+test_cmdline_SOURCES = test-cmdline.c test-common.c $(ethtool_SOURCES)
test_cmdline_CFLAGS = -DTEST_ETHTOOL
-test_one_cmdline_SOURCES = $(ethtool_SOURCES)
-test_one_cmdline_CFLAGS = -DTEST_ETHTOOL
dist-hook:
cp $(top_srcdir)/ethtool.spec $(distdir)
diff --git a/ethtool.c b/ethtool.c
index f18f611..65bdd38 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -3261,16 +3261,13 @@ static int do_getmodule(struct cmd_context *ctx)
return 0;
}
+#ifndef TEST_ETHTOOL
int send_ioctl(struct cmd_context *ctx, void *cmd)
{
-#ifndef TEST_ETHTOOL
ctx->ifr.ifr_data = cmd;
return ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr);
-#else
- /* If we get this far then parsing succeeded */
- exit(0);
-#endif
}
+#endif
static int show_usage(struct cmd_context *ctx);
@@ -3451,7 +3448,7 @@ static int show_usage(struct cmd_context *ctx)
return 0;
}
-int main(int argc, char **argp, char **envp)
+int main(int argc, char **argp)
{
int (*func)(struct cmd_context *);
int want_device;
diff --git a/internal.h b/internal.h
index 55f0d8a..10abe25 100644
--- a/internal.h
+++ b/internal.h
@@ -6,7 +6,11 @@
#ifdef HAVE_CONFIG_H
#include "ethtool-config.h"
#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/types.h>
+#include <unistd.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <net/if.h>
@@ -98,6 +102,44 @@ struct cmd_context {
#ifdef TEST_ETHTOOL
int test_cmdline(const char *args);
+
+#ifndef TEST_NO_WRAPPERS
+int test_main(int argc, char **argp);
+#define main(...) test_main(__VA_ARGS__)
+void test_exit(int rc) __attribute__((noreturn));
+#undef exit
+#define exit(rc) test_exit(rc)
+void *test_malloc(size_t size);
+#undef malloc
+#define malloc(size) test_malloc(size)
+void *test_calloc(size_t nmemb, size_t size);
+#undef calloc
+#define calloc(nmemb, size) test_calloc(nmemb, size)
+char *test_strdup(const char *s);
+#undef strdup
+#define strdup(s) test_strdup(s)
+void *test_free(void *ptr);
+#undef free
+#define free(ptr) test_free(ptr)
+void *test_realloc(void *ptr, size_t size);
+#undef realloc
+#define realloc(ptr, size) test_realloc(ptr, size)
+int test_open(const char *pathname, int flag, ...);
+#undef open
+#define open(...) test_open(__VA_ARGS__)
+int test_socket(int domain, int type, int protocol);
+#undef socket
+#define socket(...) test_socket(__VA_ARGS__)
+int test_close(int fd);
+#undef close
+#define close(fd) test_close(fd)
+FILE *test_fopen(const char *path, const char *mode);
+#undef fopen
+#define fopen(path, mode) test_fopen(path, mode)
+int test_fclose(FILE *fh);
+#undef fclose
+#define fclose(fh) test_fclose(fh)
+#endif
#endif
int send_ioctl(struct cmd_context *ctx, void *cmd);
diff --git a/test-cmdline.c b/test-cmdline.c
index c30b0e6..c62bb97 100644
--- a/test-cmdline.c
+++ b/test-cmdline.c
@@ -9,6 +9,7 @@
#include <stdio.h>
#include <stdlib.h>
+#define TEST_NO_WRAPPERS
#include "internal.h"
static struct test_case {
@@ -232,6 +233,12 @@ static struct test_case {
{ 1, "-0" },
};
+int send_ioctl(struct cmd_context *ctx, void *cmd)
+{
+ /* If we get this far then parsing succeeded */
+ test_exit(0);
+}
+
int main(void)
{
struct test_case *tc;
diff --git a/test-common.c b/test-common.c
index 4ea84c8..69853aa 100644
--- a/test-common.c
+++ b/test-common.c
@@ -2,27 +2,268 @@
* 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 <setjmp.h>
+#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
-#include <sys/wait.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;
+
+ 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;
+
+void test_exit(int rc)
+{
+ longjmp(test_return, rc + 1);
+}
+
int test_cmdline(const char *args)
{
int argc, i;
char **argv;
const char *arg;
size_t len;
- pid_t pid;
- int dev_null;
- int status;
+ int dev_null = -1, old_stdout = -1, old_stderr = -1;
int rc;
/* Convert line to argv */
@@ -37,12 +278,12 @@ int test_cmdline(const char *args)
break;
arg += len + 1;
}
- argv = calloc(argc + 1, sizeof(argv[0]));
- argv[0] = strdup("ethtool");
+ 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] = malloc(len + 1);
+ argv[i] = test_malloc(len + 1);
memcpy(argv[i], arg, len);
argv[i][len] = 0;
arg += len + 1;
@@ -56,37 +297,41 @@ int test_cmdline(const char *args)
}
fflush(NULL);
- pid = fork();
-
- /* Child */
- if (pid == 0) {
- dup2(dev_null, STDIN_FILENO);
- if (!getenv("ETHTOOL_TEST_VERBOSE")) {
- dup2(dev_null, STDOUT_FILENO);
- dup2(dev_null, STDERR_FILENO);
+ dup2(dev_null, STDIN_FILENO);
+ if (!getenv("TEST_TEST_VERBOSE")) {
+ old_stdout = dup(STDOUT_FILENO);
+ if (old_stdout < 0) {
+ perror("dup stdout");
+ rc = -1;
+ goto out;
}
- execv("./test-one-cmdline", argv);
- _exit(126);
+ dup2(dev_null, STDOUT_FILENO);
+ old_stderr = dup(STDERR_FILENO);
+ if (old_stderr < 0) {
+ perror("dup stderr");
+ rc = -1;
+ goto out;
+ }
+ dup2(dev_null, STDERR_FILENO);
}
- /* Parent */
- if (pid < 0) {
- perror("fork");
- close(dev_null);
- rc = -1;
- goto out;
+ rc = setjmp(test_return);
+ rc = rc ? rc - 1 : test_main(argc, argv);
+
+out:
+ fflush(NULL);
+ if (old_stderr >= 0) {
+ dup2(old_stderr, STDERR_FILENO);
+ close(old_stderr);
}
- close(dev_null);
- if (waitpid(pid, &status, 0) < 0) {
- perror("waitpid");
- rc = -1;
- goto out;
+ if (old_stdout >= 0) {
+ dup2(old_stdout, STDOUT_FILENO);
+ close(old_stdout);
}
- rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ if (dev_null >= 0)
+ close(dev_null);
-out:
- for (i = 0; i < argc; i++)
- free(argv[i]);
- free(argv);
+ test_free_all();
+ test_close_all();
return rc;
}
--
1.7.7.6
--
Ben Hutchings, Staff Engineer, Solarflare
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists