| /* 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; | |
| ssize_t len; | |
| 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 */ | |
| len = readlink("/proc/self/exe", path2, sizeof(path2) - 1); | |
| if (len < 0) | |
| return -1; | |
| path2[len] = '\0'; | |
| /* Follow another level of symlink if there */ | |
| if (lstat(path2, &st) == 0 && (st.st_mode & S_IFMT) == S_IFLNK) { | |
| scnprintf(path, sizeof(path), "%s", path2); | |
| len = readlink(path, path2, sizeof(path2) - 1); | |
| if (len < 0) | |
| return -1; | |
| path2[len] = '\0'; | |
| } | |
| /* 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], *line = NULL; | |
| size_t line_len = 0; | |
| ssize_t len; | |
| char *desc = NULL; | |
| const char *spdx = "SPDX-License"; | |
| io__init(&io, openat(dir_fd, name, O_RDONLY), buf, sizeof(buf)); | |
| if (io.fd < 0) | |
| return NULL; | |
| while ((len = io__getline(&io, &line, &line_len)) > 0) { | |
| char *p = line; | |
| /* Skip leading whitespace */ | |
| while (*p && isspace(*p)) | |
| p++; | |
| /* Must be a comment */ | |
| if (*p != '#') | |
| continue; | |
| p++; | |
| /* Skip shebang or SPDX lines */ | |
| if (*p == '!' || (strstr(p, spdx) && strstr(p, "-Identifier:"))) | |
| continue; | |
| /* Skip whitespace after # */ | |
| while (*p && isspace(*p)) | |
| p++; | |
| /* If we found non-empty text, this is the description! */ | |
| if (*p && *p != '\n') { | |
| char *end = p + strlen(p); | |
| while (end > p && isspace(end[-1])) | |
| end--; | |
| *end = '\0'; | |
| desc = strdup(p); | |
| break; | |
| } | |
| } | |
| free(line); | |
| close(io.fd); | |
| return desc; | |
| } | |
| /* 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; | |
| ssize_t len; | |
| char *exclusive; | |
| snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); | |
| len = readlink(link, filename, sizeof(filename) - 1); | |
| if (len < 0 || (size_t)len > sizeof(filename) - strlen(name) - 2) { | |
| pr_err("Failed to readlink %s or path too long", 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); | |
| exclusive = strstr(desc, " (exclusive)"); | |
| if (exclusive != NULL) { | |
| tests[0].exclusive = true; | |
| exclusive[0] = '\0'; | |
| } | |
| 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); | |
| close(fd); | |
| } | |
| 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; | |
| } |