diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2015-04-01 01:43:13 -0700 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2015-04-01 01:43:13 -0700 |
commit | e622e623de136222356272ef89d9d620990b546f (patch) | |
tree | ce2bb0ceb72970a3753c761752fab73b8964697c | |
parent | 29eda6468530b7555a573402a629e29993c27d9f (diff) | |
download | bluez-e622e623de136222356272ef89d9d620990b546f.tar.gz |
build: Add test-runner utility for virtual machine execution
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.tools | 3 | ||||
-rw-r--r-- | tools/test-runner.c | 431 |
3 files changed, 434 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore index 45f84ff72..776706277 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ tools/obexctl tools/gatt-service tools/btgatt-client tools/btgatt-server +tools/test-runner tools/mcaptest tools/bneptest test/sap_client.pyc diff --git a/Makefile.tools b/Makefile.tools index 833f67ac0..8faf2f4fc 100644 --- a/Makefile.tools +++ b/Makefile.tools @@ -225,7 +225,8 @@ noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \ tools/btsnoop tools/btproxy \ tools/btiotest tools/bneptest tools/mcaptest \ tools/cltest tools/oobtest tools/seq2bseq \ - tools/ibeacon tools/btgatt-client tools/btgatt-server + tools/ibeacon tools/btgatt-client tools/btgatt-server \ + tools/test-runner tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c tools_bdaddr_LDADD = lib/libbluetooth-internal.la @UDEV_LIBS@ diff --git a/tools/test-runner.c b/tools/test-runner.c new file mode 100644 index 000000000..3687b0d6d --- /dev/null +++ b/tools/test-runner.c @@ -0,0 +1,431 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <signal.h> +#include <string.h> +#include <getopt.h> +#include <poll.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/reboot.h> + +#ifndef WAIT_ANY +#define WAIT_ANY (-1) +#endif + +#define CMDLINE_MAX 2048 + +static const char *own_binary; +static char **test_argv; +static int test_argc; + +static const char *qemu_binary = NULL; +static const char *kernel_image = NULL; + +static const char *qemu_table[] = { + "/usr/bin/qemu-system-x86_64", + "/usr/bin/qemu-system-i386", + NULL +}; + +static const char *find_qemu(void) +{ + int i; + + for (i = 0; qemu_table[i]; i++) { + struct stat st; + + if (!stat(qemu_table[i], &st)) + return qemu_table[i]; + } + + return NULL; +} + +static const char *kernel_table[] = { + "bzImage", + "arch/x86/boot/bzImage", + NULL +}; + +static const char *find_kernel(void) +{ + int i; + + for (i = 0; kernel_table[i]; i++) { + struct stat st; + + if (!stat(kernel_table[i], &st)) + return kernel_table[i]; + } + + return NULL; +} + +static const struct { + const char *target; + const char *linkpath; +} dev_table[] = { + { "/proc/self/fd", "/dev/fd" }, + { "/proc/self/fd/0", "/dev/stdin" }, + { "/proc/self/fd/1", "/dev/stdout" }, + { "/proc/self/fd/2", "/dev/stderr" }, + { } +}; + +static const struct { + const char *fstype; + const char *target; + const char *options; + unsigned long flags; +} mount_table[] = { + { "sysfs", "/sys", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "proc", "/proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "devtmpfs", "/dev", "mode=755", MS_NOSUID|MS_STRICTATIME }, + { "devpts", "/dev/pts", "mode=620", MS_NOSUID|MS_NOEXEC }, + { "tmpfs", "/dev/shm", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME }, + { "tmpfs", "/run", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME }, + { } +}; + +static void prepare_sandbox(void) +{ + int i; + + for (i = 0; mount_table[i].fstype; i++) { + struct stat st; + + if (lstat(mount_table[i].target, &st) < 0) { + printf("Creating %s\n", mount_table[i].target); + mkdir(mount_table[i].target, 0755); + } + + printf("Mounting %s to %s\n", mount_table[i].fstype, + mount_table[i].target); + + if (mount(mount_table[i].fstype, + mount_table[i].target, + mount_table[i].fstype, + mount_table[i].flags, NULL) < 0) + perror("Failed to mount filesystem"); + } + + for (i = 0; dev_table[i].target; i++) { + printf("Linking %s to %s\n", dev_table[i].linkpath, + dev_table[i].target); + + if (symlink(dev_table[i].target, dev_table[i].linkpath) < 0) + perror("Failed to create device symlink"); + } + + printf("Creating new session group leader\n"); + setsid(); + + printf("Setting controlling terminal\n"); + ioctl(STDIN_FILENO, TIOCSCTTY, 1); +} + +static char *const qemu_argv[] = { + "", + "-nodefaults", + "-nodefconfig", + "-no-user-config", + "-monitor", "none", + "-display", "none", + "-machine", "type=q35,accel=kvm", + "-smbios", "type=0,uefi=on", + "-m", "192M", + "-nographic", + "-vga", "none", + "-net", "none", + "-balloon", "none", + "-no-acpi", + "-no-hpet", + "-no-reboot", + "-fsdev", "local,id=fsdev-root,path=/,readonly,security_model=none", + "-device", "virtio-9p-pci,fsdev=fsdev-root,mount_tag=/dev/root", + "-chardev", "stdio,id=chardev-serial0", + "-device", "pci-serial,chardev=chardev-serial0", + "-kernel", "", + "-append", "", + NULL +}; + +static char *const qemu_envp[] = { + NULL +}; + +static void start_qemu(void) +{ + char cwd[PATH_MAX], initcmd[PATH_MAX], testargs[PATH_MAX]; + char cmdline[CMDLINE_MAX]; + char **argv; + int i, pos; + + getcwd(cwd, sizeof(cwd)); + + if (own_binary[0] == '/') + snprintf(initcmd, sizeof(initcmd), "%s", own_binary); + else + snprintf(initcmd, sizeof(initcmd), "%s/%s", cwd, own_binary); + + if (test_argv[0][0] == '/') + pos = snprintf(testargs, sizeof(testargs), "%s", test_argv[0]); + else + pos = snprintf(testargs, sizeof(testargs), "%s/%s", + cwd, test_argv[0]); + + for (i = 1; i < test_argc; i++) { + int len = sizeof(testargs) - pos; + pos += snprintf(testargs + pos, len, " %s", test_argv[i]); + } + + snprintf(cmdline, sizeof(cmdline), + "console=ttyS0,115200n8 earlyprintk=serial " + "rootfstype=9p " + "rootflags=trans=virtio,version=9p2000.L " + "acpi=off pci=noacpi noapic quiet ro init=%s " + "TESTHOME=%s TESTARGS=\'%s\'", + initcmd, cwd, testargs); + + argv = alloca(sizeof(qemu_argv)); + memcpy(argv, qemu_argv, sizeof(qemu_argv)); + + argv[0] = (char *) qemu_binary; + + for (i = 1; argv[i]; i++) { + if (!strcmp(argv[i], "-kernel")) + argv[i + 1] = (char *) kernel_image; + else if (!strcmp(argv[i], "-append")) + argv[i + 1] = (char *) cmdline; + } + + execve(argv[0], argv, qemu_envp); +} + +static void run_command(char *cmdname, char *home) +{ + char *argv[9], *envp[3]; + int pos = 0; + pid_t pid; + + while (1) { + char *ptr; + + ptr = strchr(cmdname, ' '); + if (!ptr) { + argv[pos++] = cmdname; + break; + } + + *ptr = '\0'; + argv[pos++] = cmdname; + if (pos > 8) + break; + + cmdname = ptr + 1; + } + + argv[pos] = NULL; + + pos = 0; + envp[pos++] = "TERM=linux"; + if (home) + envp[pos++] = home; + envp[pos] = NULL; + + printf("Running command %s\n", argv[0]); + + pid = fork(); + if (pid < 0) { + perror("Failed to fork new process"); + return; + } + + if (pid == 0) { + execve(argv[0], argv, envp); + exit(EXIT_SUCCESS); + } + + printf("New process %d created\n", pid); + + while (1) { + pid_t corpse; + int status; + + corpse = waitpid(WAIT_ANY, &status, 0); + if (corpse < 0 || corpse == 0) + continue; + + printf("Process %d terminated with status=%d\n", + corpse, status); + + if (corpse == pid) + break; + } +} + +static void run_tests(void) +{ + char cmdline[CMDLINE_MAX], *ptr, *cmds, *home = NULL; + FILE *fp; + + fp = fopen("/proc/cmdline", "re"); + if (!fp) { + fprintf(stderr, "Failed to open kernel command line\n"); + return; + } + + ptr = fgets(cmdline, sizeof(cmdline), fp); + fclose(fp); + + if (!ptr) { + fprintf(stderr, "Failed to read kernel command line\n"); + return; + } + + ptr = strstr(cmdline, "TESTARGS="); + if (!ptr) { + fprintf(stderr, "No test command section found\n"); + return; + } + + cmds = ptr + 10; + ptr = strchr(cmds, '\''); + if (!ptr) { + fprintf(stderr, "Malformed test command section\n"); + return; + } + + *ptr = '\0'; + + ptr = strstr(cmdline, "TESTHOME="); + if (ptr) { + home = ptr + 4; + ptr = strpbrk(home + 9, " \r\n"); + if (ptr) + *ptr = '\0'; + } + + run_command(cmds, home); +} + +static void usage(void) +{ + printf("test-runner - Automated test execution utility\n" + "Usage:\n"); + printf("\ttest-runner [options] [--] <command> [args]\n"); + printf("Options:\n" + "\t-q, --qemu QEMU binary\n" + "\t-k, --kernel Kernel image (bzImage)\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "qemu", required_argument, NULL, 'q' }, + { "kernel", required_argument, NULL, 'k' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + if (getpid() == 1 && getppid() == 0) { + prepare_sandbox(); + run_tests(); + + sync(); + reboot(RB_AUTOBOOT); + return EXIT_SUCCESS; + } + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "q:k:vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'q': + qemu_binary = optarg; + break; + case 'k': + kernel_image = optarg; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind < 1) { + fprintf(stderr, "Failed to specify test command\n"); + return EXIT_FAILURE; + } + + own_binary = argv[0]; + test_argv = argv + optind; + test_argc = argc - optind; + + if (!qemu_binary) { + qemu_binary = find_qemu(); + if (!qemu_binary) { + fprintf(stderr, "No default QEMU binary found\n"); + return EXIT_FAILURE; + } + } + + if (!kernel_image) { + kernel_image = find_kernel(); + if (!kernel_image) { + fprintf(stderr, "No default kernel image found\n"); + return EXIT_FAILURE; + } + } + + printf("Using QEMU binary %s\n", qemu_binary); + printf("Using kernel image %s\n", kernel_image); + + start_qemu(); + + return EXIT_SUCCESS; +} |