[libvirt] [PATCH 1/3] tests: Introduce virpcitest

Michal Privoznik mprivozn at redhat.com
Fri Oct 25 13:03:41 UTC 2013


Among with this test introduce virpcimock as we need to mock some
syscalls, e.g. redirect open() of a file under /sys/bus/pci to a
stub sysfs tree.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 .gitignore         |   1 +
 cfg.mk             |   2 +-
 tests/Makefile.am  |  13 +++
 tests/virpcimock.c | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/virpcitest.c | 103 ++++++++++++++++++
 5 files changed, 430 insertions(+), 1 deletion(-)
 create mode 100644 tests/virpcimock.c
 create mode 100644 tests/virpcitest.c

diff --git a/.gitignore b/.gitignore
index e372876..6b024e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -212,6 +212,7 @@
 /tests/virlockspacetest
 /tests/virlogtest
 /tests/virnet*test
+/tests/virpcitest
 /tests/virportallocatortest
 /tests/virshtest
 /tests/virstoragetest
diff --git a/cfg.mk b/cfg.mk
index e9da282..15114a9 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -942,7 +942,7 @@ exclude_file_name_regexp--sc_bindtextdomain = ^(tests|examples)/
 exclude_file_name_regexp--sc_copyright_usage = \
   ^COPYING(|\.LESSER)$$
 
-exclude_file_name_regexp--sc_flags_usage = ^(docs/|src/util/virnetdevtap\.c$$|tests/vircgroupmock\.c$$)
+exclude_file_name_regexp--sc_flags_usage = ^(docs/|src/util/virnetdevtap\.c$$|tests/(vircgroup|virpci)mock\.c$$)
 
 exclude_file_name_regexp--sc_libvirt_unmarked_diagnostics = \
   ^(src/rpc/gendispatch\.pl$$|tests/)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 866ecd4..6d3245b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -123,6 +123,7 @@ test_programs = virshtest sockettest \
 	virauthconfigtest \
 	virbitmaptest \
 	vircgrouptest \
+	virpcitest \
 	virendiantest \
 	viridentitytest \
 	virkeycodetest \
@@ -306,6 +307,7 @@ test_libraries = libshunload.la \
 		libvirportallocatormock.la \
 		virnetserverclientmock.la \
 		vircgroupmock.la \
+		virpcimock.la \
 		$(NULL)
 if WITH_QEMU
 test_libraries += libqemumonitortestutils.la
@@ -742,6 +744,17 @@ vircgroupmock_la_CFLAGS = $(AM_CFLAGS)
 vircgroupmock_la_LDFLAGS = -module -avoid-version \
         -rpath /evil/libtool/hack/to/force/shared/lib/creation
 
+virpcitest_SOURCES = \
+	virpcitest.c testutils.h testutils.c
+virpcitest_LDADD = $(LDADDS)
+
+virpcimock_la_SOURCES = \
+	virpcimock.c
+virpcimock_la_CFLAGS = $(AM_CFLAGS)
+virpcimock_la_LIBADD = ../src/libvirt.la
+virpcimock_la_LDFLAGS = -module -avoid-version \
+        -rpath /evil/libtool/hack/to/force/shared/lib/creation
+
 if WITH_DBUS
 virdbustest_SOURCES = \
 	virdbustest.c testutils.h testutils.c
diff --git a/tests/virpcimock.c b/tests/virpcimock.c
new file mode 100644
index 0000000..d545361
--- /dev/null
+++ b/tests/virpcimock.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Michal Privoznik <mprivozn at redhat.com>
+ */
+
+#include <config.h>
+
+#ifdef __linux__
+# include "internal.h"
+# include <stdio.h>
+# include <dlfcn.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <stdarg.h>
+# include "viralloc.h"
+# include "virstring.h"
+# include "virfile.h"
+
+static int (*realaccess)(const char *path, int mode);
+static int (*reallstat)(const char *path, struct stat *sb);
+static int (*real__lxstat)(int ver, const char *path, struct stat *sb);
+static int (*realopen)(const char *path, int flags, ...);
+
+/* Don't make static, since it causes problems with clang
+ * when passed as an arg to virAsprintf()
+ * vircgroupmock.c:462:22: error: static variable 'fakesysfsdir' is used in an inline function with external linkage [-Werror,-Wstatic-in-inline]
+ */
+char *fakesysfsdir;
+
+# define PCI_SYSFS_PREFIX "/sys/bus/pci/"
+
+# define STDERR(...)                                                    \
+    fprintf(stderr, "%s %zu: ", __FUNCTION__, (size_t) __LINE__);       \
+    fprintf(stderr, __VA_ARGS__);                                       \
+    fprintf(stderr, "\n");                                              \
+
+# define ABORT(...)                                                     \
+    do {                                                                \
+        STDERR(__VA_ARGS__);                                            \
+        abort();                                                        \
+    } while (0)
+
+# define ABORT_OOM()                                                    \
+    ABORT("Out of memory")
+/*
+ * The plan:
+ *
+ * Mock some file handling functions. Redirect them into a stub tree passed via
+ * LIBVIRT_FAKE_SYSFS_DIR env variable. All files and links within stub tree is
+ * created by us.
+ */
+
+/*
+ *
+ * Functions to model kernel behavior
+ *
+ */
+
+struct pciDevice {
+    char *id;
+    int vendor;
+    int device;
+};
+
+struct pciDevice **pciDevices = NULL;
+size_t pciDevices_size = 0;
+
+static void init_env(void);
+
+static void pci_device_new_from_stub(const struct pciDevice *data);
+
+
+/*
+ * Helper functions
+ */
+static void
+make_file(const char *path,
+          const char *name,
+          const char *value)
+{
+    int fd = -1;
+    char *filepath = NULL;
+
+    if (virAsprintfQuiet(&filepath, "%s/%s", path, name) < 0)
+        ABORT_OOM();
+
+    if ((fd = realopen(filepath, O_CREAT|O_WRONLY, 0666)) < 0)
+        ABORT("Unable to open: %s", filepath);
+
+    if (value && safewrite(fd, value, strlen(value)) != strlen(value))
+        ABORT("Unable to write: %s", filepath);
+
+    VIR_FORCE_CLOSE(fd);
+    VIR_FREE(filepath);
+}
+
+static int
+getrealpath(char **newpath,
+            const char *path)
+{
+    if (!fakesysfsdir)
+        init_env();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        if (virAsprintfQuiet(newpath, "%s/%s",
+                             fakesysfsdir,
+                             path + strlen(PCI_SYSFS_PREFIX)) < 0) {
+            errno = ENOMEM;
+            return -1;
+        }
+    } else {
+        if (VIR_STRDUP_QUIET(*newpath, path) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+/*
+ * PCI Device functions
+ */
+static void
+pci_device_new_from_stub(const struct pciDevice *data)
+{
+    struct pciDevice *dev;
+    char *devpath;
+    char tmp[32];
+
+    if (VIR_ALLOC_QUIET(dev) < 0 ||
+        virAsprintfQuiet(&devpath, "%s/devices/%s", fakesysfsdir, data->id) < 0)
+        ABORT_OOM();
+
+    memcpy(dev, data, sizeof(*dev));
+
+    if (virFileMakePath(devpath) < 0)
+        ABORT("Unable to create: %s", devpath);
+
+    make_file(devpath, "config", "some dummy config");
+
+    if (snprintf(tmp, sizeof(tmp),  "0x%.4x", dev->vendor) < 0)
+        ABORT("@tmp overflow");
+    make_file(devpath, "vendor", tmp);
+
+    if (snprintf(tmp, sizeof(tmp),  "0x%.4x", dev->device) < 0)
+        ABORT("@tmp overflow");
+    make_file(devpath, "device", tmp);
+
+    if (VIR_APPEND_ELEMENT_QUIET(pciDevices, pciDevices_size, dev) < 0)
+        ABORT_OOM();
+
+    VIR_FREE(devpath);
+}
+
+
+/*
+ * Functions to load the symbols and init the environment
+ */
+static void
+init_syms(void)
+{
+    if (realaccess)
+        return;
+
+# define LOAD_SYM(name)                                                 \
+    do {                                                                \
+        if (!(real ## name = dlsym(RTLD_NEXT, #name)))                  \
+            ABORT("Cannot find real '%s' symbol\n", #name);             \
+    } while (0)
+
+# define LOAD_SYM_ALT(name1, name2)                                     \
+    do {                                                                \
+        if (!(real ## name1 = dlsym(RTLD_NEXT, #name1)) &&              \
+            !(real ## name2 = dlsym(RTLD_NEXT, #name2)))                \
+            ABORT("Cannot find real '%s' or '%s' symbol\n",             \
+                  #name1, #name2);                                      \
+    } while (0)
+
+    LOAD_SYM(access);
+    LOAD_SYM_ALT(lstat, __lxstat);
+    LOAD_SYM(open);
+}
+
+static void
+init_env(void)
+{
+    if (fakesysfsdir)
+        return;
+
+    if (!(fakesysfsdir = getenv("LIBVIRT_FAKE_SYSFS_DIR")))
+        ABORT("Missing LIBVIRT_FAKE_SYSFS_DIR env variable\n");
+
+# define MAKE_PCI_DEVICE(Id, Vendor, Device, ...)                       \
+    do {                                                                \
+        struct pciDevice dev = {.id = (char *)Id, .vendor = Vendor,     \
+                                .device = Device, __VA_ARGS__};         \
+        pci_device_new_from_stub(&dev);                                 \
+    } while (0)
+
+    MAKE_PCI_DEVICE("0000:00:00.0", 0x8086, 0x0044);
+}
+
+
+/*
+ *
+ * Mocked functions
+ *
+ */
+
+int
+access(const char *path, int mode)
+{
+    int ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return -1;
+        ret = realaccess(newpath, mode);
+        VIR_FREE(newpath);
+    } else {
+        ret = realaccess(path, mode);
+    }
+    return ret;
+}
+
+int
+__lxstat(int ver, const char *path, struct stat *sb)
+{
+    int ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return -1;
+        ret = real__lxstat(ver, newpath, sb);
+        VIR_FREE(newpath);
+    } else {
+        ret = real__lxstat(ver, path, sb);
+    }
+    return ret;
+}
+
+int
+lstat(const char *path, struct stat *sb)
+{
+    int ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return -1;
+        ret = reallstat(newpath, sb);
+        VIR_FREE(newpath);
+    } else {
+        ret = reallstat(path, sb);
+    }
+    return ret;
+}
+
+int
+open(const char *path, int flags, ...)
+{
+    int ret;
+    char *newpath = NULL;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX) &&
+        getrealpath(&newpath, path) < 0)
+        return -1;
+
+    if (flags & O_CREAT) {
+        va_list ap;
+        mode_t mode;
+        va_start(ap, flags);
+        mode = va_arg(ap, mode_t);
+        va_end(ap);
+        ret = realopen(newpath ? newpath : path, flags, mode);
+    } else {
+        ret = realopen(newpath ? newpath : path, flags);
+    }
+    VIR_FREE(newpath);
+    return ret;
+}
+
+#else
+/* Nothing to override on non-__linux__ platforms */
+#endif
diff --git a/tests/virpcitest.c b/tests/virpcitest.c
new file mode 100644
index 0000000..96f11d6
--- /dev/null
+++ b/tests/virpcitest.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Michal Privoznik <mprivozn at redhat.com>
+ */
+
+#include <config.h>
+
+#include "testutils.h"
+
+#ifdef __linux__
+
+# include <stdlib.h>
+# include <stdio.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <virpci.h>
+
+# define VIR_FROM_THIS VIR_FROM_NONE
+
+# define FAKESYSFSDIRTEMPLATE abs_builddir "/fakesysfsdir-XXXXXX"
+char *fakesysfsdir;
+
+static int
+testVirPCIDeviceNew(const void *opaque ATTRIBUTE_UNUSED)
+{
+    int ret = -1;
+    virPCIDevicePtr dev;
+    const char *devName;
+
+    if (!(dev = virPCIDeviceNew(0, 0, 0, 0)))
+        goto cleanup;
+
+    devName = virPCIDeviceGetName(dev);
+    if (STRNEQ(devName, "0000:00:00.0")) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       "PCI device name mismatch: %s, expecting %s",
+                       devName, "0000:00:00.0");
+        goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    virPCIDeviceFree(dev);
+    return ret;
+}
+
+static int
+mymain(void)
+{
+    int ret = 0;
+
+    if (VIR_STRDUP_QUIET(fakesysfsdir, FAKESYSFSDIRTEMPLATE) < 0) {
+        fprintf(stderr, "Out of memory\n");
+        abort();
+    }
+
+    if (!mkdtemp(fakesysfsdir)) {
+        fprintf(stderr, "Cannot create fakesysfsdir");
+        abort();
+    }
+
+    setenv("LIBVIRT_FAKE_SYSFS_DIR", fakesysfsdir, 1);
+
+# define DO_TEST(fnc)                                   \
+    do {                                                \
+        if (virtTestRun(#fnc, fnc, NULL) < 0)           \
+            ret = -1;                                   \
+    } while (0)
+
+    DO_TEST(testVirPCIDeviceNew);
+
+    if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL)
+        virFileDeleteTree(fakesysfsdir);
+
+    VIR_FREE(fakesysfsdir);
+
+    return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virpcimock.so")
+#else
+int
+main(void)
+{
+    return EXIT_AM_SKIP;
+}
+#endif
-- 
1.8.1.5




More information about the libvir-list mailing list