diff options
Diffstat (limited to 'kernel/tests/lib/tst_res.c')
-rw-r--r-- | kernel/tests/lib/tst_res.c | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/kernel/tests/lib/tst_res.c b/kernel/tests/lib/tst_res.c new file mode 100644 index 0000000..c35f41b --- /dev/null +++ b/kernel/tests/lib/tst_res.c @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * AUTHOR : Kent Rogers (from Dave Fenner's original) + * CO-PILOT : Rich Logan + * DATE STARTED : 05/01/90 (rewritten 1/96) + * Copyright (c) 2009-2016 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ + +#define _GNU_SOURCE + +#include <pthread.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "test.h" +#include "safe_macros.h" +#include "usctest.h" +#include "ltp_priv.h" +#include "tst_ansi_color.h" + +long TEST_RETURN; +int TEST_ERRNO; +void *TST_RET_PTR; + +#define VERBOSE 1 +#define NOPASS 3 +#define DISCARD 4 + +#define MAXMESG 80 /* max length of internal messages */ +#define USERMESG 2048 /* max length of user message */ +#define TRUE 1 +#define FALSE 0 + +/* + * EXPAND_VAR_ARGS - Expand the variable portion (arg_fmt) of a result + * message into the specified string. + * + * NOTE (garrcoop): arg_fmt _must_ be the last element in each function + * argument list that employs this. + */ +#define EXPAND_VAR_ARGS(buf, arg_fmt, buf_len) do {\ + va_list ap; \ + assert(arg_fmt != NULL); \ + va_start(ap, arg_fmt); \ + vsnprintf(buf, buf_len, arg_fmt, ap); \ + va_end(ap); \ + assert(strlen(buf) > 0); \ +} while (0) + +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# ifdef __ANDROID__ +# define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP \ + PTHREAD_RECURSIVE_MUTEX_INITIALIZER +# else +/* MUSL: http://www.openwall.com/lists/musl/2017/02/20/5 */ +# define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP { {PTHREAD_MUTEX_RECURSIVE} } +# endif +#endif + +static pthread_mutex_t tmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +static void check_env(void); +static void tst_condense(int tnum, int ttype, const char *tmesg); +static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg); + +static int T_exitval = 0; /* exit value used by tst_exit() */ +static int passed_cnt; +static int T_mode = VERBOSE; /* flag indicating print mode: VERBOSE, */ + /* NOPASS, DISCARD */ + +static char Warn_mesg[MAXMESG]; /* holds warning messages */ + +/* + * These are used for condensing output when NOT in verbose mode. + */ +static int Buffered = FALSE; /* TRUE if condensed output is currently */ + /* buffered (i.e. not yet printed) */ +static char *Last_tcid; /* previous test case id */ +static int Last_num; /* previous test case number */ +static int Last_type; /* previous test result type */ +static char *Last_mesg; /* previous test result message */ + +int tst_count = 0; + +/* + * These globals must be defined in the test. + */ +extern char *TCID; /* Test case identifier from the test source */ +extern int TST_TOTAL; /* Total number of test cases from the test */ + + +struct pair { + const char *name; + int val; +}; + +#define PAIR(def) [def] = {.name = #def, .val = def}, +#define STRPAIR(key, value) [key] = {.name = value, .val = key}, + +#define PAIR_LOOKUP(pair_arr, idx) do { \ + if (idx < 0 || (size_t)idx >= ARRAY_SIZE(pair_arr) || \ + pair_arr[idx].name == NULL) \ + return "???"; \ + return pair_arr[idx].name; \ +} while (0) + +const char *strttype(int ttype) +{ + static const struct pair ttype_pairs[] = { + PAIR(TPASS) + PAIR(TFAIL) + PAIR(TBROK) + PAIR(TCONF) + PAIR(TWARN) + PAIR(TINFO) + }; + + PAIR_LOOKUP(ttype_pairs, TTYPE_RESULT(ttype)); +} + +#include "errnos.h" +#include "signame.h" + +static void tst_res__(const char *file, const int lineno, int ttype, + const char *arg_fmt, ...) +{ + pthread_mutex_lock(&tmutex); + + char tmesg[USERMESG]; + int len = 0; + int ttype_result = TTYPE_RESULT(ttype); + + if (file && (ttype_result != TPASS && ttype_result != TINFO)) + len = sprintf(tmesg, "%s:%d: ", file, lineno); + EXPAND_VAR_ARGS(tmesg + len, arg_fmt, USERMESG - len); + + /* + * Save the test result type by ORing ttype into the current exit + * value (used by tst_exit()). + */ + T_exitval |= ttype_result; + + if (ttype_result == TPASS) + passed_cnt++; + + check_env(); + + /* + * Set the test case number and print the results, depending on the + * display type. + */ + if (ttype_result == TWARN || ttype_result == TINFO) { + tst_print(TCID, 0, ttype, tmesg); + } else { + if (tst_count < 0) + tst_print(TCID, 0, TWARN, + "tst_res(): tst_count < 0 is not valid"); + + /* + * Process each display type. + */ + switch (T_mode) { + case DISCARD: + break; + case NOPASS: /* filtered by tst_print() */ + tst_condense(tst_count + 1, ttype, tmesg); + break; + default: /* VERBOSE */ + tst_print(TCID, tst_count + 1, ttype, tmesg); + break; + } + + tst_count++; + } + + pthread_mutex_unlock(&tmutex); +} + +static void tst_condense(int tnum, int ttype, const char *tmesg) +{ + int ttype_result = TTYPE_RESULT(ttype); + + /* + * If this result is the same as the previous result, return. + */ + if (Buffered == TRUE) { + if (strcmp(Last_tcid, TCID) == 0 && Last_type == ttype_result && + strcmp(Last_mesg, tmesg) == 0) + return; + + /* + * This result is different from the previous result. First, + * print the previous result. + */ + tst_print(Last_tcid, Last_num, Last_type, Last_mesg); + free(Last_tcid); + free(Last_mesg); + } + + /* + * If a file was specified, print the current result since we have no + * way of retaining the file contents for comparing with future + * results. Otherwise, buffer the current result info for next time. + */ + Last_tcid = malloc(strlen(TCID) + 1); + strcpy(Last_tcid, TCID); + Last_num = tnum; + Last_type = ttype_result; + Last_mesg = malloc(strlen(tmesg) + 1); + strcpy(Last_mesg, tmesg); + Buffered = TRUE; +} + +void tst_old_flush(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + pthread_mutex_lock(&tmutex); + + /* + * Print out last line if in NOPASS mode. + */ + if (Buffered == TRUE && T_mode == NOPASS) { + tst_print(Last_tcid, Last_num, Last_type, Last_mesg); + Buffered = FALSE; + } + + fflush(stdout); + + pthread_mutex_unlock(&tmutex); +} + +static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg) +{ + int err = errno; + const char *type; + int ttype_result = TTYPE_RESULT(ttype); + char message[USERMESG]; + size_t size = 0; + + /* + * Save the test result type by ORing ttype into the current exit value + * (used by tst_exit()). This is already done in tst_res(), but is + * also done here to catch internal warnings. For internal warnings, + * tst_print() is called directly with a case of TWARN. + */ + T_exitval |= ttype_result; + + /* + * If output mode is DISCARD, or if the output mode is NOPASS and this + * result is not one of FAIL, BROK, or WARN, just return. This check + * is necessary even though we check for DISCARD mode inside of + * tst_res(), since occasionally we get to this point without going + * through tst_res() (e.g. internal TWARN messages). + */ + if (T_mode == DISCARD || (T_mode == NOPASS && ttype_result != TFAIL && + ttype_result != TBROK + && ttype_result != TWARN)) + return; + + /* + * Build the result line and print it. + */ + type = strttype(ttype); + + if (T_mode == VERBOSE) { + size += snprintf(message + size, sizeof(message) - size, + "%-8s %4d ", tcid, tnum); + } else { + size += snprintf(message + size, sizeof(message) - size, + "%-8s %4d ", tcid, tnum); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (tst_color_enabled(STDOUT_FILENO)) + size += snprintf(message + size, sizeof(message) - size, + "%s%s%s : %s", tst_ttype2color(ttype), type, ANSI_COLOR_RESET, tmesg); + else + size += snprintf(message + size, sizeof(message) - size, + "%s : %s", type, tmesg); + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TERRNO) { + size += snprintf(message + size, sizeof(message) - size, + ": errno=%s(%i): %s", tst_strerrno(err), + err, strerror(err)); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TTERRNO) { + size += snprintf(message + size, sizeof(message) - size, + ": TEST_ERRNO=%s(%i): %s", + tst_strerrno(TEST_ERRNO), (int)TEST_ERRNO, + strerror(TEST_ERRNO)); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TRERRNO) { + err = TEST_RETURN < 0 ? -(int)TEST_RETURN : (int)TEST_RETURN; + size += snprintf(message + size, sizeof(message) - size, + ": TEST_RETURN=%s(%i): %s", + tst_strerrno(err), err, strerror(err)); + } + + if (size + 1 >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + message[size] = '\n'; + message[size + 1] = '\0'; + + fputs(message, stdout); +} + +static void check_env(void) +{ + static int first_time = 1; + char *value; + + if (!first_time) + return; + + first_time = 0; + + /* BTOUTPUT not defined, use default */ + if ((value = getenv(TOUTPUT)) == NULL) { + T_mode = VERBOSE; + return; + } + + if (strcmp(value, TOUT_NOPASS_S) == 0) { + T_mode = NOPASS; + return; + } + + if (strcmp(value, TOUT_DISCARD_S) == 0) { + T_mode = DISCARD; + return; + } + + T_mode = VERBOSE; + return; +} + +void tst_exit(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + pthread_mutex_lock(&tmutex); + + tst_old_flush(); + + T_exitval &= ~TINFO; + + if (T_exitval == TCONF && passed_cnt) + T_exitval &= ~TCONF; + + exit(T_exitval); +} + +pid_t tst_fork(void) +{ + pid_t child; + + NO_NEWLIB_ASSERT("Unknown", 0); + + tst_old_flush(); + + child = fork(); + if (child == 0) + T_exitval = 0; + + return child; +} + +void tst_record_childstatus(void (*cleanup)(void), pid_t child) +{ + int status, ttype_result; + + NO_NEWLIB_ASSERT("Unknown", 0); + + SAFE_WAITPID(cleanup, child, &status, 0); + + if (WIFEXITED(status)) { + ttype_result = WEXITSTATUS(status); + ttype_result = TTYPE_RESULT(ttype_result); + T_exitval |= ttype_result; + + if (ttype_result == TPASS) + tst_resm(TINFO, "Child process returned TPASS"); + + if (ttype_result & TFAIL) + tst_resm(TINFO, "Child process returned TFAIL"); + + if (ttype_result & TBROK) + tst_resm(TINFO, "Child process returned TBROK"); + + if (ttype_result & TCONF) + tst_resm(TINFO, "Child process returned TCONF"); + + } else { + tst_brkm(TBROK, cleanup, "child process(%d) killed by " + "unexpected signal %s(%d)", child, + tst_strsig(WTERMSIG(status)), WTERMSIG(status)); + } +} + +pid_t tst_vfork(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + tst_old_flush(); + return vfork(); +} + +/* + * Make tst_brk reentrant so that one can call the SAFE_* macros from within + * user-defined cleanup functions. + */ +static int tst_brk_entered = 0; + +static void tst_brk__(const char *file, const int lineno, int ttype, + void (*func)(void), const char *arg_fmt, ...) +{ + pthread_mutex_lock(&tmutex); + + char tmesg[USERMESG]; + int ttype_result = TTYPE_RESULT(ttype); + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + /* + * Only FAIL, BROK, CONF, and RETR are supported by tst_brk(). + */ + if (ttype_result != TFAIL && ttype_result != TBROK && + ttype_result != TCONF) { + sprintf(Warn_mesg, "%s: Invalid Type: %d. Using TBROK", + __func__, ttype_result); + tst_print(TCID, 0, TWARN, Warn_mesg); + /* Keep TERRNO, TTERRNO, etc. */ + ttype = (ttype & ~ttype_result) | TBROK; + } + + tst_res__(file, lineno, ttype, "%s", tmesg); + if (tst_brk_entered == 0) { + if (ttype_result == TCONF) { + tst_res__(file, lineno, ttype, + "Remaining cases not appropriate for " + "configuration"); + } else if (ttype_result == TBROK) { + tst_res__(file, lineno, TBROK, + "Remaining cases broken"); + } + } + + /* + * If no cleanup function was specified, just return to the caller. + * Otherwise call the specified function. + */ + if (func != NULL) { + tst_brk_entered++; + (*func) (); + tst_brk_entered--; + } + if (tst_brk_entered == 0) + tst_exit(); + + pthread_mutex_unlock(&tmutex); +} + +void tst_resm_(const char *file, const int lineno, int ttype, + const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + if (tst_test) + tst_res_(file, lineno, ttype, "%s", tmesg); + else + tst_res__(file, lineno, ttype, "%s", tmesg); +} + +typedef void (*tst_res_func_t)(const char *file, const int lineno, + int ttype, const char *fmt, ...); + +void tst_resm_hexd_(const char *file, const int lineno, int ttype, + const void *buf, size_t size, const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + static const size_t symb_num = 2; /* xx */ + static const size_t size_max = 16; + size_t offset; + size_t i; + char *pmesg = tmesg; + tst_res_func_t res_func; + + if (tst_test) + res_func = tst_res_; + else + res_func = tst_res__; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + offset = strlen(tmesg); + + if (size > size_max || size == 0 || + (offset + size * (symb_num + 1)) >= USERMESG) + res_func(file, lineno, ttype, "%s", tmesg); + else + pmesg += offset; + + for (i = 0; i < size; ++i) { + /* add space before byte except first one */ + if (pmesg != tmesg) + *(pmesg++) = ' '; + + sprintf(pmesg, "%02x", ((unsigned char *)buf)[i]); + pmesg += symb_num; + if ((i + 1) % size_max == 0 || i + 1 == size) { + res_func(file, lineno, ttype, "%s", tmesg); + pmesg = tmesg; + } + } +} + +void tst_brkm_(const char *file, const int lineno, int ttype, + void (*func)(void), const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + if (tst_test) { + if (func) { + tst_brk_(file, lineno, TBROK, + "Non-NULL cleanup in newlib!"); + } + + tst_brk_(file, lineno, ttype, "%s", tmesg); + } else { + tst_brk__(file, lineno, ttype, func, "%s", tmesg); + } + + /* Shouldn't be reached, but fixes build time warnings about noreturn. */ + abort(); +} + +void tst_require_root(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + if (geteuid() != 0) + tst_brkm(TCONF, NULL, "Test needs to be run as root"); +} |