| /* SPDX-License-Identifier: GPL-2.0 */ |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/ctype.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/zalloc.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <subcmd/exec-cmd.h> |
| #include <subcmd/parse-options.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #include <api/io.h> |
| #include "builtin.h" |
| #include "tests-scripts.h" |
| #include "color.h" |
| #include "debug.h" |
| #include "hist.h" |
| #include "intlist.h" |
| #include "string2.h" |
| #include "symbol.h" |
| #include "tests.h" |
| #include "util/rlimit.h" |
| #include "util/util.h" |
| |
| static int shell_tests__dir_fd(void) |
| { |
| struct stat st; |
| char path[PATH_MAX], path2[PATH_MAX], *exec_path; |
| static const char * const devel_dirs[] = { |
| "./tools/perf/tests/shell", |
| "./tests/shell", |
| "./source/tests/shell" |
| }; |
| int fd; |
| char *p; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(devel_dirs); ++i) { |
| fd = open(devel_dirs[i], O_PATH); |
| |
| if (fd >= 0) |
| return fd; |
| } |
| |
| /* Use directory of executable */ |
| if (readlink("/proc/self/exe", path2, sizeof path2) < 0) |
| return -1; |
| /* Follow another level of symlink if there */ |
| if (lstat(path2, &st) == 0 && (st.st_mode & S_IFMT) == S_IFLNK) { |
| scnprintf(path, sizeof(path), path2); |
| if (readlink(path, path2, sizeof path2) < 0) |
| return -1; |
| } |
| /* Get directory */ |
| p = strrchr(path2, '/'); |
| if (p) |
| *p = 0; |
| scnprintf(path, sizeof(path), "%s/tests/shell", path2); |
| fd = open(path, O_PATH); |
| if (fd >= 0) |
| return fd; |
| scnprintf(path, sizeof(path), "%s/source/tests/shell", path2); |
| fd = open(path, O_PATH); |
| if (fd >= 0) |
| return fd; |
| |
| /* Then installed path. */ |
| exec_path = get_argv_exec_path(); |
| scnprintf(path, sizeof(path), "%s/tests/shell", exec_path); |
| free(exec_path); |
| return open(path, O_PATH); |
| } |
| |
| static char *shell_test__description(int dir_fd, const char *name) |
| { |
| struct io io; |
| char buf[128], desc[256]; |
| int ch, pos = 0; |
| |
| io__init(&io, openat(dir_fd, name, O_RDONLY), buf, sizeof(buf)); |
| if (io.fd < 0) |
| return NULL; |
| |
| /* Skip first line - should be #!/bin/sh Shebang */ |
| if (io__get_char(&io) != '#') |
| goto err_out; |
| if (io__get_char(&io) != '!') |
| goto err_out; |
| do { |
| ch = io__get_char(&io); |
| if (ch < 0) |
| goto err_out; |
| } while (ch != '\n'); |
| |
| do { |
| ch = io__get_char(&io); |
| if (ch < 0) |
| goto err_out; |
| } while (ch == '#' || isspace(ch)); |
| while (ch > 0 && ch != '\n') { |
| desc[pos++] = ch; |
| if (pos >= (int)sizeof(desc) - 1) |
| break; |
| ch = io__get_char(&io); |
| } |
| while (pos > 0 && isspace(desc[--pos])) |
| ; |
| desc[++pos] = '\0'; |
| close(io.fd); |
| return strdup(desc); |
| err_out: |
| close(io.fd); |
| return NULL; |
| } |
| |
| /* Is this full file path a shell script */ |
| static bool is_shell_script(int dir_fd, const char *path) |
| { |
| const char *ext; |
| |
| ext = strrchr(path, '.'); |
| if (!ext) |
| return false; |
| if (!strcmp(ext, ".sh")) { /* Has .sh extension */ |
| if (faccessat(dir_fd, path, R_OK | X_OK, 0) == 0) /* Is executable */ |
| return true; |
| } |
| return false; |
| } |
| |
| /* Is this file in this dir a shell script (for test purposes) */ |
| static bool is_test_script(int dir_fd, const char *name) |
| { |
| return is_shell_script(dir_fd, name); |
| } |
| |
| /* Duplicate a string and fall over and die if we run out of memory */ |
| static char *strdup_check(const char *str) |
| { |
| char *newstr; |
| |
| newstr = strdup(str); |
| if (!newstr) { |
| pr_err("Out of memory while duplicating test script string\n"); |
| abort(); |
| } |
| return newstr; |
| } |
| |
| static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) |
| { |
| const char *file = test->priv; |
| int err; |
| char *cmd = NULL; |
| |
| if (asprintf(&cmd, "%s%s", file, verbose ? " -v" : "") < 0) |
| return TEST_FAIL; |
| err = system(cmd); |
| free(cmd); |
| if (!err) |
| return TEST_OK; |
| |
| return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; |
| } |
| |
| static void append_script(int dir_fd, const char *name, char *desc, |
| struct test_suite ***result, |
| size_t *result_sz) |
| { |
| char filename[PATH_MAX], link[128]; |
| struct test_suite *test_suite, **result_tmp; |
| struct test_case *tests; |
| size_t len; |
| |
| snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); |
| len = readlink(link, filename, sizeof(filename)); |
| if (len < 0) { |
| pr_err("Failed to readlink %s", link); |
| return; |
| } |
| filename[len++] = '/'; |
| strcpy(&filename[len], name); |
| |
| tests = calloc(2, sizeof(*tests)); |
| if (!tests) { |
| pr_err("Out of memory while building script test suite list\n"); |
| return; |
| } |
| tests[0].name = strdup_check(name); |
| tests[0].desc = strdup_check(desc); |
| tests[0].run_case = shell_test__run; |
| |
| test_suite = zalloc(sizeof(*test_suite)); |
| if (!test_suite) { |
| pr_err("Out of memory while building script test suite list\n"); |
| free(tests); |
| return; |
| } |
| test_suite->desc = desc; |
| test_suite->test_cases = tests; |
| test_suite->priv = strdup_check(filename); |
| /* Realloc is good enough, though we could realloc by chunks, not that |
| * anyone will ever measure performance here */ |
| result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); |
| if (result_tmp == NULL) { |
| pr_err("Out of memory while building script test suite list\n"); |
| free(tests); |
| free(test_suite); |
| return; |
| } |
| /* Add file to end and NULL terminate the struct array */ |
| *result = result_tmp; |
| (*result)[*result_sz] = test_suite; |
| (*result_sz)++; |
| } |
| |
| static void append_scripts_in_dir(int dir_fd, |
| struct test_suite ***result, |
| size_t *result_sz) |
| { |
| struct dirent **entlist; |
| struct dirent *ent; |
| int n_dirs, i; |
| |
| /* List files, sorted by alpha */ |
| n_dirs = scandirat(dir_fd, ".", &entlist, NULL, alphasort); |
| if (n_dirs == -1) |
| return; |
| for (i = 0; i < n_dirs && (ent = entlist[i]); i++) { |
| int fd; |
| |
| if (ent->d_name[0] == '.') |
| continue; /* Skip hidden files */ |
| if (is_test_script(dir_fd, ent->d_name)) { /* It's a test */ |
| char *desc = shell_test__description(dir_fd, ent->d_name); |
| |
| if (desc) /* It has a desc line - valid script */ |
| append_script(dir_fd, ent->d_name, desc, result, result_sz); |
| continue; |
| } |
| if (ent->d_type != DT_DIR) { |
| struct stat st; |
| |
| if (ent->d_type != DT_UNKNOWN) |
| continue; |
| fstatat(dir_fd, ent->d_name, &st, 0); |
| if (!S_ISDIR(st.st_mode)) |
| continue; |
| } |
| if (strncmp(ent->d_name, "base_", 5) == 0) |
| continue; /* Skip scripts that have a separate driver. */ |
| fd = openat(dir_fd, ent->d_name, O_PATH); |
| append_scripts_in_dir(fd, result, result_sz); |
| } |
| for (i = 0; i < n_dirs; i++) /* Clean up */ |
| zfree(&entlist[i]); |
| free(entlist); |
| } |
| |
| struct test_suite **create_script_test_suites(void) |
| { |
| struct test_suite **result = NULL, **result_tmp; |
| size_t result_sz = 0; |
| int dir_fd = shell_tests__dir_fd(); /* Walk dir */ |
| |
| /* |
| * Append scripts if fd is good, otherwise return a NULL terminated zero |
| * length array. |
| */ |
| if (dir_fd >= 0) |
| append_scripts_in_dir(dir_fd, &result, &result_sz); |
| |
| result_tmp = realloc(result, (result_sz + 1) * sizeof(*result_tmp)); |
| if (result_tmp == NULL) { |
| pr_err("Out of memory while building script test suite list\n"); |
| abort(); |
| } |
| /* NULL terminate the test suite array. */ |
| result = result_tmp; |
| result[result_sz] = NULL; |
| if (dir_fd >= 0) |
| close(dir_fd); |
| return result; |
| } |