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(a)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 1b2fd46..89b5613 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -946,7 +946,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(a)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(a)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