diff options
Diffstat (limited to 'lib/igt_core.c')
-rw-r--r-- | lib/igt_core.c | 901 |
1 files changed, 901 insertions, 0 deletions
diff --git a/lib/igt_core.c b/lib/igt_core.c new file mode 100644 index 00000000..27c159c6 --- /dev/null +++ b/lib/igt_core.c @@ -0,0 +1,901 @@ +/* + * Copyright © 2007, 2011, 2013, 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Eric Anholt <eric@anholt.net> + * Daniel Vetter <daniel.vetter@ffwll.ch> + * + */ + +#ifndef ANDROID +#define _GNU_SOURCE +#else +#include <libgen.h> +#endif +#include <stdio.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <string.h> +#include <sys/mman.h> +#include <signal.h> +#include <pciaccess.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <sys/utsname.h> +#include <termios.h> + +#include "drmtest.h" +#include "i915_drm.h" +#include "intel_chipset.h" +#include "intel_gpu_tools.h" +#include "igt_debugfs.h" +#include "../version.h" +#include "config.h" + +#include "igt_core.h" + +/** + * SECTION:igt_core + * @short_description: Core i-g-t testing support + * @title: i-g-t core + * + * This libary implements the core of the i-g-t test support infrastructure. + * Main features are the subtest enumeration, cmdline option parsing helpers for + * subtest handling and various helpers to structure testcases with subtests and + * handle subtest test results. + * + * Auxiliary code provides exit handlers, support for forked processes with test + * result propagation. Other generally useful functionality includes optional + * structure logging infrastructure and some support code for running reduced + * test set on in simulated hardware environments. + */ + +static unsigned int exit_handler_count; + +/* subtests helpers */ +static bool list_subtests = false; +static char *run_single_subtest = NULL; +static const char *in_subtest = NULL; +static bool in_fixture = false; +static bool test_with_subtests = false; +static enum { + CONT = 0, SKIP, FAIL +} skip_subtests_henceforth = CONT; + +/* fork support state */ +pid_t *test_children; +int num_test_children; +int test_children_sz; +bool test_child; + +bool __igt_fixture(void) +{ + assert(!in_fixture); + + if (igt_only_list_subtests()) + return false; + + if (skip_subtests_henceforth) + return false; + + in_fixture = true; + return true; +} + +void __igt_fixture_complete(void) +{ + assert(in_fixture); + + in_fixture = false; +} + +void __igt_fixture_end(void) +{ + assert(in_fixture); + + in_fixture = false; + longjmp(igt_subtest_jmpbuf, 1); +} + +bool igt_exit_called; +static void check_igt_exit(int sig) +{ + /* When not killed by a signal check that igt_exit() has been properly + * called. */ + assert(sig != 0 || igt_exit_called); +} + +static void print_version(void) +{ + struct utsname uts; + + if (list_subtests) + return; + + uname(&uts); + + fprintf(stdout, "IGT-Version: %s-%s (%s) (%s: %s %s)\n", PACKAGE_VERSION, + IGT_GIT_SHA1, TARGET_CPU_PLATFORM, + uts.sysname, uts.release, uts.machine); +} + +static void print_usage(const char *command_str, const char *help_str, + bool output_on_stderr) +{ + FILE *f = output_on_stderr ? stderr : stdout; + + fprintf(f, "Usage: %s [OPTIONS]\n" + " --list-subtests\n" + " --run-subtest <pattern>\n", command_str); + if (help_str) + fprintf(f, "%s\n", help_str); +} + +static void oom_adjust_for_doom(void) +{ + int fd; + const char always_kill[] = "1000"; + + fd = open("/proc/self/oom_score_adj", O_WRONLY); + igt_assert(fd != -1); + igt_assert(write(fd, always_kill, sizeof(always_kill)) == sizeof(always_kill)); +} + +int igt_subtest_init_parse_opts(int argc, char **argv, + const char *extra_short_opts, + struct option *extra_long_opts, + const char *help_str, + igt_opt_handler_t extra_opt_handler) +{ + int c, option_index = 0; + static struct option long_options[] = { + {"list-subtests", 0, 0, 'l'}, + {"run-subtest", 1, 0, 'r'}, + {"help", 0, 0, 'h'}, + }; + const char *command_str; + char *short_opts; + struct option *combined_opts; + int extra_opt_count; + int all_opt_count; + int ret = 0; + + test_with_subtests = true; + + command_str = argv[0]; + if (strrchr(command_str, '/')) + command_str = strrchr(command_str, '/') + 1; + + /* First calculate space for all passed-in extra long options */ + all_opt_count = 0; + while (extra_long_opts && extra_long_opts[all_opt_count].name) + all_opt_count++; + extra_opt_count = all_opt_count; + + all_opt_count += ARRAY_SIZE(long_options); + + combined_opts = malloc(all_opt_count * sizeof(*combined_opts)); + memcpy(combined_opts, extra_long_opts, + extra_opt_count * sizeof(*combined_opts)); + + /* Copy the subtest long options (and the final NULL entry) */ + memcpy(&combined_opts[extra_opt_count], long_options, + ARRAY_SIZE(long_options) * sizeof(*combined_opts)); + + ret = asprintf(&short_opts, "%sh", + extra_short_opts ? extra_short_opts : ""); + assert(ret >= 0); + + while ((c = getopt_long(argc, argv, short_opts, combined_opts, + &option_index)) != -1) { + switch(c) { + case 'l': + if (!run_single_subtest) + list_subtests = true; + break; + case 'r': + if (!list_subtests) + run_single_subtest = strdup(optarg); + break; + case 'h': + print_usage(command_str, help_str, false); + ret = -1; + goto out; + case '?': + if (opterr) { + print_usage(command_str, help_str, true); + ret = -2; + goto out; + } + /* + * Just ignore the error, since the unknown argument + * can be something the caller understands and will + * parse by doing a second getopt scanning. + */ + break; + default: + ret = extra_opt_handler(c, option_index); + if (ret) + goto out; + } + } + + igt_install_exit_handler(check_igt_exit); + oom_adjust_for_doom(); + +out: + free(short_opts); + free(combined_opts); + print_version(); + + return ret; +} + +enum igt_log_level igt_log_level = IGT_LOG_INFO; + +static void common_init(void) +{ + char *env = getenv("IGT_LOG_LEVEL"); + + if (!env) + return; + + if (strcmp(env, "debug") == 0) + igt_log_level = IGT_LOG_DEBUG; + else if (strcmp(env, "info") == 0) + igt_log_level = IGT_LOG_INFO; + else if (strcmp(env, "warn") == 0) + igt_log_level = IGT_LOG_WARN; + else if (strcmp(env, "none") == 0) + igt_log_level = IGT_LOG_NONE; +} + +void igt_subtest_init(int argc, char **argv) +{ + int ret; + + /* supress getopt errors about unknown options */ + opterr = 0; + + ret = igt_subtest_init_parse_opts(argc, argv, NULL, NULL, NULL, NULL); + if (ret < 0) + /* exit with no error for -h/--help */ + exit(ret == -1 ? 0 : ret); + + /* reset opt parsing */ + optind = 1; + + common_init(); +} + +void igt_simple_init(void) +{ + print_version(); + + oom_adjust_for_doom(); + + common_init(); +} + +/* + * Note: Testcases which use these helpers MUST NOT output anything to stdout + * outside of places protected by igt_run_subtest checks - the piglit + * runner adds every line to the subtest list. + */ +bool __igt_run_subtest(const char *subtest_name) +{ + assert(!in_subtest); + assert(!in_fixture); + + if (list_subtests) { + printf("%s\n", subtest_name); + return false; + } + + if (run_single_subtest && + strcmp(subtest_name, run_single_subtest) != 0) + return false; + + if (skip_subtests_henceforth) { + printf("Subtest %s: %s\n", subtest_name, + skip_subtests_henceforth == SKIP ? + "SKIP" : "FAIL"); + return false; + } + + return (in_subtest = subtest_name); +} + +const char *igt_subtest_name(void) +{ + return in_subtest; +} + +bool igt_only_list_subtests(void) +{ + return list_subtests; +} + +static bool skipped_one = false; +static bool succeeded_one = false; +static bool failed_one = false; +static int igt_exitcode; + +static void exit_subtest(const char *) __attribute__((noreturn)); +static void exit_subtest(const char *result) +{ + printf("Subtest %s: %s\n", in_subtest, result); + in_subtest = NULL; + longjmp(igt_subtest_jmpbuf, 1); +} + +void igt_skip(const char *f, ...) +{ + va_list args; + skipped_one = true; + + assert(!test_child); + + if (!igt_only_list_subtests()) { + va_start(args, f); + vprintf(f, args); + va_end(args); + } + + if (in_subtest) { + exit_subtest("SKIP"); + } else if (test_with_subtests) { + skip_subtests_henceforth = SKIP; + assert(in_fixture); + __igt_fixture_end(); + } else { + exit(77); + } +} + +void __igt_skip_check(const char *file, const int line, + const char *func, const char *check, + const char *f, ...) +{ + va_list args; + int err = errno; + + if (f) { + static char *buf; + + /* igt_skip never returns, so try to not leak too badly. */ + if (buf) + free(buf); + + va_start(args, f); + vasprintf(&buf, f, args); + va_end(args); + + igt_skip("Test requirement not met in function %s, file %s:%i:\n" + "Last errno: %i, %s\n" + "Test requirement: (%s)\n%s", + func, file, line, err, strerror(err), check, buf); + } else { + igt_skip("Test requirement not met in function %s, file %s:%i:\n" + "Last errno: %i, %s\n" + "Test requirement: (%s)\n", + func, file, line, err, strerror(err), check); + } +} + +void igt_success(void) +{ + succeeded_one = true; + if (in_subtest) + exit_subtest("SUCCESS"); +} + +void igt_fail(int exitcode) +{ + assert(exitcode != 0 && exitcode != 77); + + if (!failed_one) + igt_exitcode = exitcode; + + failed_one = true; + + /* Silent exit, parent will do the yelling. */ + if (test_child) + exit(exitcode); + + if (in_subtest) + exit_subtest("FAIL"); + else { + assert(!test_with_subtests || in_fixture); + + if (in_fixture) { + skip_subtests_henceforth = FAIL; + __igt_fixture_end(); + } + + exit(exitcode); + } +} + +static bool run_under_gdb(void) +{ + char buf[1024]; + + sprintf(buf, "/proc/%d/exe", getppid()); + return (readlink (buf, buf, sizeof (buf)) != -1 && + strncmp(basename(buf), "gdb", 3) == 0); +} + +void __igt_fail_assert(int exitcode, const char *file, + const int line, const char *func, const char *assertion, + const char *f, ...) +{ + va_list args; + int err = errno; + + printf("Test assertion failure function %s, file %s:%i:\n" + "Last errno: %i, %s\n" + "Failed assertion: %s\n", + func, file, line, err, strerror(err), assertion); + + if (f) { + va_start(args, f); + vprintf(f, args); + va_end(args); + } + + if (run_under_gdb()) + abort(); + igt_fail(exitcode); +} + +void igt_exit(void) +{ + igt_exit_called = true; + + if (igt_only_list_subtests()) + exit(0); + + if (!test_with_subtests) + exit(0); + + /* Calling this without calling one of the above is a failure */ + assert(skipped_one || succeeded_one || failed_one); + + if (failed_one) + exit(igt_exitcode); + else if (succeeded_one) + exit(0); + else + exit(77); +} + +/* fork support code */ +static int helper_process_count; +static pid_t helper_process_pids[] = +{ -1, -1, -1, -1}; + +static void reset_helper_process_list(void) +{ + for (int i = 0; i < ARRAY_SIZE(helper_process_pids); i++) + helper_process_pids[i] = -1; + helper_process_count = 0; +} + +static void fork_helper_exit_handler(int sig) +{ + for (int i = 0; i < ARRAY_SIZE(helper_process_pids); i++) { + pid_t pid = helper_process_pids[i]; + int status, ret; + + if (pid != -1) { + /* Someone forgot to fill up the array? */ + assert(pid != 0); + + ret = kill(pid, SIGQUIT); + assert(ret == 0); + while (waitpid(pid, &status, 0) == -1 && + errno == EINTR) + ; + helper_process_count--; + } + } + + assert(helper_process_count == 0); +} + +bool __igt_fork_helper(struct igt_helper_process *proc) +{ + pid_t pid; + int id; + + assert(!proc->running); + assert(helper_process_count < ARRAY_SIZE(helper_process_pids)); + + for (id = 0; helper_process_pids[id] != -1; id++) + ; + + igt_install_exit_handler(fork_helper_exit_handler); + + switch (pid = fork()) { + case -1: + igt_assert(0); + case 0: + exit_handler_count = 0; + reset_helper_process_list(); + oom_adjust_for_doom(); + + return true; + default: + proc->running = true; + proc->pid = pid; + proc->id = id; + helper_process_pids[id] = pid; + helper_process_count++; + + return false; + } + +} + +void igt_stop_helper(struct igt_helper_process *proc) +{ + int status, ret; + + assert(proc->running); + + ret = kill(proc->pid, + proc->use_SIGKILL ? SIGKILL : SIGQUIT); + assert(ret == 0); + while (waitpid(proc->pid, &status, 0) == -1 && + errno == EINTR) + ; + igt_assert(WIFSIGNALED(status) && + WTERMSIG(status) == (proc->use_SIGKILL ? SIGKILL : SIGQUIT)); + + proc->running = false; + + helper_process_pids[proc->id] = -1; + helper_process_count--; +} + +void igt_wait_helper(struct igt_helper_process *proc) +{ + int status; + + assert(proc->running); + + while (waitpid(proc->pid, &status, 0) == -1 && + errno == EINTR) + ; + igt_assert(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + proc->running = false; + + helper_process_pids[proc->id] = -1; + helper_process_count--; +} + +static void children_exit_handler(int sig) +{ + int ret; + + assert(!test_child); + + for (int nc = 0; nc < num_test_children; nc++) { + int status = -1; + ret = kill(test_children[nc], SIGQUIT); + assert(ret == 0); + + while (waitpid(test_children[nc], &status, 0) == -1 && + errno == EINTR) + ; + } + + num_test_children = 0; +} + +bool __igt_fork(void) +{ + assert(!test_with_subtests || in_subtest); + assert(!test_child); + + igt_install_exit_handler(children_exit_handler); + + if (num_test_children >= test_children_sz) { + if (!test_children_sz) + test_children_sz = 4; + else + test_children_sz *= 2; + + test_children = realloc(test_children, + sizeof(pid_t)*test_children_sz); + igt_assert(test_children); + } + + switch (test_children[num_test_children++] = fork()) { + case -1: + igt_assert(0); + case 0: + test_child = true; + exit_handler_count = 0; + reset_helper_process_list(); + oom_adjust_for_doom(); + + return true; + default: + return false; + } + +} + +/** + * igt_waitchildren: + * + * Wait for all children forked with igt_fork + * + * The magic here is that exit codes from children will be correctly propagated + */ +void igt_waitchildren(void) +{ + assert(!test_child); + + for (int nc = 0; nc < num_test_children; nc++) { + int status = -1; + while (waitpid(test_children[nc], &status, 0) == -1 && + errno == EINTR) + ; + + if (status != 0) { + if (WIFEXITED(status)) { + printf("child %i failed with exit status %i\n", + nc, WEXITSTATUS(status)); + igt_fail(WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("child %i died with signal %i, %s\n", + nc, WTERMSIG(status), + strsignal(WTERMSIG(status))); + igt_fail(99); + } else { + printf("Unhandled failure in child %i\n", nc); + abort(); + } + } + } + + num_test_children = 0; +} + +/* exit handler code */ +#define MAX_SIGNALS 32 +#define MAX_EXIT_HANDLERS 5 + +static struct { + sighandler_t handler; + bool installed; +} orig_sig[MAX_SIGNALS]; + +static igt_exit_handler_t exit_handler_fn[MAX_EXIT_HANDLERS]; +static bool exit_handler_disabled; +static sigset_t saved_sig_mask; +static const int handled_signals[] = + { SIGINT, SIGHUP, SIGTERM, SIGQUIT, SIGPIPE, SIGABRT, SIGSEGV, SIGBUS }; + +static int install_sig_handler(int sig_num, sighandler_t handler) +{ + orig_sig[sig_num].handler = signal(sig_num, handler); + + if (orig_sig[sig_num].handler == SIG_ERR) + return -1; + + orig_sig[sig_num].installed = true; + + return 0; +} + +static void restore_sig_handler(int sig_num) +{ + /* Just restore the default so that we properly fall over. */ + signal(sig_num, SIG_DFL); +} + +static void restore_all_sig_handler(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(orig_sig); i++) + restore_sig_handler(i); +} + +static void call_exit_handlers(int sig) +{ + int i; + + if (!exit_handler_count) { + return; + } + + for (i = exit_handler_count - 1; i >= 0; i--) + exit_handler_fn[i](sig); + + /* ensure we don't get called twice */ + exit_handler_count = 0; +} + +static void igt_atexit_handler(void) +{ + restore_all_sig_handler(); + + if (!exit_handler_disabled) + call_exit_handlers(0); +} + +static void fatal_sig_handler(int sig) +{ + pid_t pid, tid; + + restore_all_sig_handler(); + + /* + * exit_handler_disabled is always false here, since when we set it + * we also block signals. + */ + call_exit_handlers(sig); + + /* Workaround cached PID and TID races on glibc and Bionic libc. */ + pid = syscall(SYS_getpid); + tid = syscall(SYS_gettid); + + syscall(SYS_tgkill, pid, tid, sig); +} + +/* + * Set a handler that will be called either when the process calls exit() or + * returns from the main function, or one of the signals in 'handled_signals' + * is raised. MAX_EXIT_HANDLERS handlers can be installed, each of which will + * be called only once, even if a subsequent signal is raised. If the exit + * handlers are called due to a signal, the signal will be re-raised with the + * original signal disposition after all handlers returned. + * + * The handler will be passed the signal number if called due to a signal, or + * 0 otherwise. + */ +void igt_install_exit_handler(igt_exit_handler_t fn) +{ + int i; + + for (i = 0; i < exit_handler_count; i++) + if (exit_handler_fn[i] == fn) + return; + + igt_assert(exit_handler_count < MAX_EXIT_HANDLERS); + + exit_handler_fn[exit_handler_count] = fn; + exit_handler_count++; + + if (exit_handler_count > 1) + return; + + for (i = 0; i < ARRAY_SIZE(handled_signals); i++) { + if (install_sig_handler(handled_signals[i], + fatal_sig_handler)) + goto err; + } + + if (atexit(igt_atexit_handler)) + goto err; + + return; +err: + restore_all_sig_handler(); + exit_handler_count--; + + igt_assert_f(0, "failed to install the signal handler\n"); +} + +void igt_disable_exit_handler(void) +{ + sigset_t set; + int i; + + if (exit_handler_disabled) + return; + + sigemptyset(&set); + for (i = 0; i < ARRAY_SIZE(handled_signals); i++) + sigaddset(&set, handled_signals[i]); + + if (sigprocmask(SIG_BLOCK, &set, &saved_sig_mask)) { + perror("sigprocmask"); + return; + } + + exit_handler_disabled = true; +} + +void igt_enable_exit_handler(void) +{ + if (!exit_handler_disabled) + return; + + if (sigprocmask(SIG_SETMASK, &saved_sig_mask, NULL)) { + perror("sigprocmask"); + return; + } + + exit_handler_disabled = false; +} + +/* simulation enviroment support */ +bool igt_run_in_simulation(void) +{ + static int simulation = -1; + + if (simulation == -1) + simulation = igt_env_set("INTEL_SIMULATION", false); + + return simulation; +} + +/** + * igt_skip_on_simulation: + * + * Skip tests when INTEL_SIMULATION env war is set + * + * Skip the test when running on simulation (and that's relevant only when + * we're not in the mode where we list the subtests). + * + * This function is subtest aware (since it uses igt_skip) and so can be used to + * skip specific subtests or all subsequent subtests. + */ +void igt_skip_on_simulation(void) +{ + if (igt_only_list_subtests()) + return; + + igt_require(!igt_run_in_simulation()); +} + +/* structured logging */ +void igt_log(enum igt_log_level level, const char *format, ...) +{ + va_list args; + + assert(format); + + if (igt_log_level > level) + return; + + va_start(args, format); + if (level == IGT_LOG_WARN) { + fflush(stdout); + vfprintf(stderr, format, args); + } else + vprintf(format, args); + va_end(args); +} + |