From d770618842bb5727394ada7ce4e2c502044a88fe Mon Sep 17 00:00:00 2001 From: Michal Privoznik Date: Wed, 23 Oct 2013 14:44:40 +0100 Subject: [PATCH] tests: Introduce virpcitest 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 --- .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 e37287618b..6b024e7ff6 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 1b2fd464cc..d7998c89e9 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/vir(cgroup|pci)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 866ecd41e6..6d3245b3bb 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 0000000000..9e69c3dda9 --- /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 + * . + * + * Author: Michal Privoznik + */ + +#include + +#ifdef __linux__ +# include "internal.h" +# include +# include +# include +# include +# include +# include +# include +# 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 nPciDevices = 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, nPciDevices, 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 0000000000..295dd9b427 --- /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 + * . + * + * Author: Michal Privoznik + */ + +#include + +#include "testutils.h" + +#ifdef __linux__ + +# include +# include +# include +# include +# include +# include + +# define VIR_FROM_THIS VIR_FROM_NONE + +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; +} + +# define FAKESYSFSDIRTEMPLATE abs_builddir "/fakesysfsdir-XXXXXX" + +static int +mymain(void) +{ + int ret = 0; + char *fakesysfsdir; + + 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