[PATCH v1 01/31] lib: Add tst_sysfs_assert
Cyril Hrubis <[email protected]>
| Newsgroups | gmane.linux.ltp |
|---|---|
| Message-ID | <[email protected]> |
Adds helpers for a sysfs testscases. Signed-off-by: Cyril Hrubis <[email protected]> --- doc/developers/api_c_tests.rst | 5 + include/tst_sysfs_assert.h | 588 ++++++++++++++++++++++++ lib/tst_sysfs_assert.c | 797 +++++++++++++++++++++++++++++++++ 3 files changed, 1390 insertions(+) create mode 100644 include/tst_sysfs_assert.h create mode 100644 lib/tst_sysfs_assert.c diff --git a/doc/developers/api_c_tests.rst b/doc/developers/api_c_tests.rst index 1e9e595dd..a0a9ee2ba 100644 --- a/doc/developers/api_c_tests.rst +++ b/doc/developers/api_c_tests.rst @@ -105,3 +105,8 @@ Test macros Uinput ------ .. kernel-doc:: ../../include/tse_uinput.h + +Sysfs +----- + +.. kernel-doc:: ../../include/tst_sysfs_assert.h diff --git a/include/tst_sysfs_assert.h b/include/tst_sysfs_assert.h new file mode 100644 index 000000000..6cef5271a --- /dev/null +++ b/include/tst_sysfs_assert.h @@ -0,0 +1,588 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2026 Cyril Hrubis <[email protected]> + */ + +#ifndef TST_SYSFS_ASSERT_H__ +#define TST_SYSFS_ASSERT_H__ + +#include <stddef.h> + +/** + * TST_SYSFS_ASSERT_RANGELL() - Asserts that min <= val <= max. + * + * The val is read from a path constructed from the fmt. Both bounds are long + * int values, hence the LL (long, long) suffix. + * + * @min: A long int minimal value. + * @max: A long int maximal value. + * @fmt: A printf-like format to build path to the file to read the val from. + * @...: A printf-like parameters for the path. + */ +#define TST_SYSFS_ASSERT_RANGELL(min, max, fmt, ...) \ + tst_sysfs_assert_rangell(__FILE__, __LINE__, min, max, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_rangell(const char *file, const int lineno, + long min, long max, const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_RANGELL_SILENT() - Asserts that min <= val <= max. + * + * The val is read from a path constructed from the fmt. Both bounds are long + * int values, hence the LL (long, long) suffix. + * + * Silently skips the check if the file path constructed from fmt does not + * exist. + * + * @min: A minimal value. + * @max: A maximal value. + * @fmt: A printf-like format to build path to the file to read the val from. + * @...: A printf-like parameters for the path. + */ +#define TST_SYSFS_ASSERT_RANGELL_SILENT(min, max, fmt, ...) \ + tst_sysfs_assert_rangell_silent(__FILE__, __LINE__, min, max, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_rangell_silent(const char *file, const int lineno, + long min, long max, const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_RANGELF() - Asserts that min <= val <= max. + * + * The min bound is a long int, while the max bound is read from a file, hence + * the LF (long, file) suffix. The value and max paths are built by appending + * val_suffix/max_suffix to a common prefix built from fmt. + * + * Skips with TCONF if either file does not exist. + * + * @min: A minimal value. + * @val_suffix: A filename the val is read from. + * @max_suffix: A filename the max is read from. + * @fmt: A printf-like format to build the path to val and max from. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_RANGELF(min, val_suffix, max_suffix, fmt, ...) \ + tst_sysfs_assert_rangelf(__FILE__, __LINE__, min, val_suffix, \ + max_suffix, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_rangelf(const char *file, const int lineno, + long min, const char *val_suffix, const char *max_suffix, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_BOOL() - Asserts that file has boolean value. + * + * Reads a value from the file at the path built from fmt and asserts + * that it is a boolean (0 or 1). + * + * Skips with TCONF if the file does not exist. + * + * @fmt: A printf-like format to build a path to the boolean file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_BOOL(fmt, ...) \ + tst_sysfs_assert_bool(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_bool(const char *file, const int lineno, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_BOOL_SILENT() - Asserts that file has boolean value. + * + * Reads a value from the file at the path built from fmt and asserts + * that it is a boolean (0 or 1). + * + * Silently skips the check if the file path constructed from fmt does not + * exist. + * + * @fmt: A printf-like format to build a path to the boolean file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_BOOL_SILENT(fmt, ...) \ + tst_sysfs_assert_bool_silent(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_bool_silent(const char *file, const int lineno, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_POW2() - Asserts that file has number that is power of two. + * + * Reads a long value from the file at the path built from fmt and asserts + * that it is a power of two, i.e. value > 0 and (value & (value - 1)) == 0. + * + * Skips with TCONF if the file does not exist. + * + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like params for fmt. + */ +#define TST_SYSFS_ASSERT_POW2(fmt, ...) \ + tst_sysfs_assert_pow2(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_pow2(const char *file, const int lineno, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_CHOICE() - Asserts that file is a list of choices. + * + * Validates a bracketed-choice file such as: + * + * "none mq-deadline kyber [bfq]" + * "[always] madvise never" + * + * Asserts that exactly one token is [selected]. If allowed is non-NULL (a + * NULL-terminated array of strings) it also asserts that every token is a + * member of the allowed set. If sel is non-NULL the selected token (with the + * brackets stripped) is copied into sel, truncated to sel_size. + * + * Skips with TCONF if the file does not exist. + * + * @allowed: Optional NULL terminated array of allowed choices. + * @sel: A buffer to copy the selected choice into. + * @sel_size: A size of the sel buffer. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_CHOICE(allowed, sel, sel_size, fmt, ...) \ + tst_sysfs_assert_choice(__FILE__, __LINE__, allowed, sel, sel_size, \ + fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_choice(const char *file, const int lineno, + const char *const allowed[], char *sel, size_t sel_size, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_TOKENS() - Asserts that file is a list of tokens. + * + * Validates a file that lists a whitespace-separated set of tokens without any + * bracketed "current" selection, such as /sys/power/state: + * + * "freeze mem disk" + * + * Asserts that the file is non-empty and, if allowed is non-NULL, that every + * token is a member of the allowed set (a NULL-terminated array of strings). + * + * If find is non-NULL it additionally asserts that find itself was seen + * among the tokens, which is useful for cross-checking a "current" value + * read from a different file against this file's list, e.g.: + * + * TST_SYSFS_ASSERT_TOKENS(NULL, current_clocksource, + * "%s/available_clocksource", name); + * + * Skips with TCONF if the file does not exist. + * + * @allowed: Optional NULL terminated array of allowed tokens. + * @find: Optional token that must be present in the file, or NULL to skip + * this check. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_TOKENS(allowed, find, fmt, ...) \ + tst_sysfs_assert_tokens(__FILE__, __LINE__, allowed, find, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_tokens(const char *file, const int lineno, + const char *const allowed[], const char *find, const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_ONEOF() - Asserts that file has one of allowed strings. + * + * Reads a single whitespace-delimited string token from the file at the path + * built from fmt and asserts that it is a member of the allowed set (a + * NULL-terminated array of strings), e.g. the cache "type" being one of + * Data/Instruction/Unified. + * + * Skips with TCONF if the file does not exist. + * + * @allowed: A NULL terminated array of allowed strings. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_ONEOF(allowed, fmt, ...) \ + tst_sysfs_assert_oneof(__FILE__, __LINE__, allowed, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_oneof(const char *file, const int lineno, + const char *const allowed[], const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_PARSE_LIST() - Parses a cpulist/nodelist-style file. + * + * Parses a file such as "0-1,3", which is used by many sysfs attributes (cpu + * online/present/possible, node online/possible, ...). + * + * On success it stores the number of set entries into count (if non-NULL) + * and the highest id present into max_id (if non-NULL) and returns 0. An + * empty list yields count == 0 and max_id == -1. + * + * On failure it reports the reason itself and returns non-zero: TCONF if the + * file does not exist, TFAIL if the content cannot be parsed. The caller + * only needs to check the return value to decide whether to skip any + * further checks that depend on the count/max_id values. + * + * @count: A pointer to store the number of set entries to, or NULL. + * @max_id: A pointer to store the highest id present to, or NULL. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_PARSE_LIST(count, max_id, fmt, ...) \ + tst_sysfs_assert_parse_list(__FILE__, __LINE__, count, max_id, \ + fmt, ##__VA_ARGS__) + +int tst_sysfs_assert_parse_list(const char *file, const int lineno, + int *count, int *max_id, const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_LIST_SUBSET() - Asserts a cpulist/nodelist is a subset. + * + * Asserts that the cpulist/nodelist in sub_path is a subset of the list in + * super_path, i.e. every id set in sub_path is also set in super_path. + * + * Skips with TCONF if either file does not exist. + * + * @sub_path: A path to the file with the presumed subset list. + * @super_path: A path to the file with the presumed superset list. + */ +#define TST_SYSFS_ASSERT_LIST_SUBSET(sub_path, super_path) \ + tst_sysfs_assert_list_subset(__FILE__, __LINE__, sub_path, super_path) + +void tst_sysfs_assert_list_subset(const char *file, const int lineno, + const char *sub_path, const char *super_path); + +/** + * TST_SYSFS_ASSERT_LIST_CONTAINS() - Asserts that id is in a cpulist/nodelist. + * + * Asserts that id is a member of the cpulist/nodelist in the file at the + * path built from fmt, e.g. that a given CPU is listed in its own + * thread_siblings_list. + * + * Skips with TCONF if the file does not exist. + * + * @id: The id that must be present in the list. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_LIST_CONTAINS(id, fmt, ...) \ + tst_sysfs_assert_list_contains(__FILE__, __LINE__, id, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_list_contains(const char *file, const int lineno, + int id, const char *fmt, ...); + +/** + * enum tst_sysfs_cmp - Comparison operators used by tst_sysfs_assert_cmp(). + * + * @TST_SYSFS_CMP_EQ: val1 == val2 + * @TST_SYSFS_CMP_LT: val1 < val2 + * @TST_SYSFS_CMP_LE: val1 <= val2 + */ +enum tst_sysfs_cmp { + TST_SYSFS_CMP_EQ, + TST_SYSFS_CMP_LT, + TST_SYSFS_CMP_LE, +}; + +void tst_sysfs_assert_cmp(const char *file, const int lineno, + const char *path1, enum tst_sysfs_cmp op, const char *path2); + +/** + * TST_SYSFS_ASSERT_EQ() - Asserts that val1 == val2. + * + * Reads a long value from each of path1 and path2 and asserts that they are + * equal. Skips with TCONF if either file does not exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_EQ(path1, path2) \ + tst_sysfs_assert_cmp(__FILE__, __LINE__, path1, TST_SYSFS_CMP_EQ, path2) + +/** + * TST_SYSFS_ASSERT_LT() - Asserts that val1 < val2. + * + * Reads a long value from each of path1 and path2 and asserts that val1 is + * less than val2. Skips with TCONF if either file does not exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_LT(path1, path2) \ + tst_sysfs_assert_cmp(__FILE__, __LINE__, path1, TST_SYSFS_CMP_LT, path2) + +/** + * TST_SYSFS_ASSERT_LE() - Asserts that val1 <= val2. + * + * Reads a long value from each of path1 and path2 and asserts that val1 is + * less than or equal to val2. Skips with TCONF if either file does not + * exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_LE(path1, path2) \ + tst_sysfs_assert_cmp(__FILE__, __LINE__, path1, TST_SYSFS_CMP_LE, path2) + +void tst_sysfs_assert_cmp_silent(const char *file, const int lineno, + const char *path1, enum tst_sysfs_cmp op, const char *path2); + +/** + * TST_SYSFS_ASSERT_EQ_SILENT() - Asserts that val1 == val2. + * + * Same as TST_SYSFS_ASSERT_EQ(), but silently skips the check if either file + * does not exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_EQ_SILENT(path1, path2) \ + tst_sysfs_assert_cmp_silent(__FILE__, __LINE__, path1, TST_SYSFS_CMP_EQ, path2) + +/** + * TST_SYSFS_ASSERT_LT_SILENT() - Asserts that val1 < val2. + * + * Same as TST_SYSFS_ASSERT_LT(), but silently skips the check if either file + * does not exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_LT_SILENT(path1, path2) \ + tst_sysfs_assert_cmp_silent(__FILE__, __LINE__, path1, TST_SYSFS_CMP_LT, path2) + +/** + * TST_SYSFS_ASSERT_LE_SILENT() - Asserts that val1 <= val2. + * + * Same as TST_SYSFS_ASSERT_LE(), but silently skips the check if either file + * does not exist. + * + * @path1: A path to the file with val1. + * @path2: A path to the file with val2. + */ +#define TST_SYSFS_ASSERT_LE_SILENT(path1, path2) \ + tst_sysfs_assert_cmp_silent(__FILE__, __LINE__, path1, TST_SYSFS_CMP_LE, path2) + +void tst_sysfs_assert_cmp_suffix(const char *file, const int lineno, + enum tst_sysfs_cmp op, const char *lo_suffix, const char *hi_suffix, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_EQ_SUFFIX() - Asserts that val1 == val2. + * + * Same as TST_SYSFS_ASSERT_EQ(), but path1/path2 are built by appending + * lo_suffix/hi_suffix to a common prefix built from fmt. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_EQ_SUFFIX(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix(__FILE__, __LINE__, TST_SYSFS_CMP_EQ, \ + lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +/** + * TST_SYSFS_ASSERT_LT_SUFFIX() - Asserts that val1 < val2. + * + * Same as TST_SYSFS_ASSERT_LT(), but path1/path2 are built by appending + * lo_suffix/hi_suffix to a common prefix built from fmt. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_LT_SUFFIX(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix(__FILE__, __LINE__, TST_SYSFS_CMP_LT, \ + lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +/** + * TST_SYSFS_ASSERT_LE_SUFFIX() - Asserts that val1 <= val2. + * + * Same as TST_SYSFS_ASSERT_LE(), but path1/path2 are built by appending + * lo_suffix/hi_suffix to a common prefix built from fmt. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_LE_SUFFIX(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix(__FILE__, __LINE__, TST_SYSFS_CMP_LE, \ + lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +void tst_sysfs_assert_cmp_suffix_silent(const char *file, const int lineno, + enum tst_sysfs_cmp op, const char *lo_suffix, const char *hi_suffix, + const char *fmt, ...); + +/** + * TST_SYSFS_ASSERT_EQ_SUFFIX_SILENT() - Asserts that val1 == val2. + * + * Same as TST_SYSFS_ASSERT_EQ_SUFFIX(), but silently skips the check if + * either file does not exist. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_EQ_SUFFIX_SILENT(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix_silent(__FILE__, __LINE__, \ + TST_SYSFS_CMP_EQ, lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +/** + * TST_SYSFS_ASSERT_LT_SUFFIX_SILENT() - Asserts that val1 < val2. + * + * Same as TST_SYSFS_ASSERT_LT_SUFFIX(), but silently skips the check if + * either file does not exist. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_LT_SUFFIX_SILENT(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix_silent(__FILE__, __LINE__, \ + TST_SYSFS_CMP_LT, lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +/** + * TST_SYSFS_ASSERT_LE_SUFFIX_SILENT() - Asserts that val1 <= val2. + * + * Same as TST_SYSFS_ASSERT_LE_SUFFIX(), but silently skips the check if + * either file does not exist. + * + * @lo_suffix: A suffix appended to fmt to build the path to val1. + * @hi_suffix: A suffix appended to fmt to build the path to val2. + * @fmt: A printf-like format to build the common path prefix. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_ASSERT_LE_SUFFIX_SILENT(lo_suffix, hi_suffix, fmt, ...) \ + tst_sysfs_assert_cmp_suffix_silent(__FILE__, __LINE__, \ + TST_SYSFS_CMP_LE, lo_suffix, hi_suffix, fmt, ##__VA_ARGS__) + +/** + * tst_sysfs_exists() - Checks whether a file exists. + * + * + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + * return: 1 if the file at the path built from fmt exists, 0 otherwise. + */ +int tst_sysfs_exists(const char *fmt, ...); + +/** + * TST_SYSFS_READ_STR() - Reads a string from a file. + * + * Reads a string from the file at the path built from fmt into buf, tolerating + * empty/whitespace-only files like SAFE_FILE_READ_STR() does, and stripping a + * trailing newline. + * + * Returns 1 and fills buf if the file exists, or 0 and leaves buf untouched + * if it does not (no TCONF or other message is printed either way). + * + * @buf: A buffer to read the string into. + * @buf_size: A size of the buf buffer. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_READ_STR(buf, buf_size, fmt, ...) \ + tst_sysfs_read_str(__FILE__, __LINE__, buf, buf_size, fmt, ##__VA_ARGS__) + +int tst_sysfs_read_str(const char *file, const int lineno, char *buf, + size_t buf_size, const char *fmt, ...); + +/** + * TST_SYSFS_EXP_EQ_LI() - Asserts that a file contains a given number. + * + * Reads a long value from the file at the path built from fmt and asserts that + * it equals val. Reports TPASS/TFAIL with the resolved path and both values, + * or TFAIL if the file does not exist or does not contain a number. + * + * This combines a numeric read and a TST_EXP_EQ_LI() style comparison in a + * single call, so tests do not have to build the path and read the value + * into a temporary variable themselves. + * + * @val: The expected long int value. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_EXP_EQ_LI(val, fmt, ...) \ + tst_sysfs_exp_eq_li(__FILE__, __LINE__, val, fmt, ##__VA_ARGS__) + +void tst_sysfs_exp_eq_li(const char *file, const int lineno, long val, + const char *fmt, ...); + +/** + * TST_SYSFS_EXP_EQ_STR() - Asserts that a file contains a given string. + * + * Reads a single whitespace-delimited string token from the file at the path + * built from fmt and asserts that it equals val. Reports TPASS/TFAIL with the + * resolved path and both values, or TCONF if the file does not exist. + * + * This combines a string read and a TST_EXP_EQ_STR() style comparison in a + * single call, so tests do not have to build the path and read the value + * into a temporary buffer themselves. + * + * @val: The expected string value. + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_EXP_EQ_STR(val, fmt, ...) \ + tst_sysfs_exp_eq_str(__FILE__, __LINE__, val, fmt, ##__VA_ARGS__) + +void tst_sysfs_exp_eq_str(const char *file, const int lineno, const char *val, + const char *fmt, ...); + +/** + * TST_SYSFS_READ_LLI() - Reads a long long from a file. + * + * Reads a long long value from the file at the path built from fmt and + * returns it. + * + * Aborts with TBROK if the file does not exist or does not contain a valid + * number: unlike most of the other checks in this header, this is not + * expected to be an optional attribute the caller should gracefully skip, + * see SAFE_FILE_SCANF(). + * + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_READ_LLI(fmt, ...) \ + tst_sysfs_read_lli(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +long long tst_sysfs_read_lli(const char *file, const int lineno, + const char *fmt, ...); + +/** + * TST_SYSFS_READ_LI() - Reads a long from a file. + * + * Reads a long value from the file at the path built from fmt and returns it. + * + * Aborts with TBROK if the file does not exist or does not contain a valid + * number: unlike most of the other checks in this header, this is not + * expected to be an optional attribute the caller should gracefully skip, + * see SAFE_FILE_SCANF(). + * + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_READ_LI(fmt, ...) \ + tst_sysfs_read_li(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +long tst_sysfs_read_li(const char *file, const int lineno, + const char *fmt, ...); + +/** + * TST_SYSFS_READ_LX() - Reads a hexadecimal unsigned long from a file. + * + * Reads a long value from the file at the path built from fmt and returns it. + * + * Aborts with TBROK if the file does not exist or does not contain a valid + * number: unlike most of the other checks in this header, this is not + * expected to be an optional attribute the caller should gracefully skip, + * see SAFE_FILE_SCANF(). + * + * @fmt: A printf-like format to build a path to the file. + * @...: A printf-like parameters for fmt. + */ +#define TST_SYSFS_READ_LX(fmt, ...) \ + tst_sysfs_read_lx(__FILE__, __LINE__, fmt, ##__VA_ARGS__) + +unsigned long tst_sysfs_read_lx(const char *file, const int lineno, + const char *fmt, ...); + +#endif /* TST_SYSFS_ASSERT_H__ */ diff --git a/lib/tst_sysfs_assert.c b/lib/tst_sysfs_assert.c new file mode 100644 index 000000000..02acdee7a --- /dev/null +++ b/lib/tst_sysfs_assert.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Cyril Hrubis <[email protected]> + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_sysfs_assert.h" + +static void build_path(char *path, size_t size, const char *fmt, va_list ap) +{ + vsnprintf(path, size, fmt, ap); +} + +static void build_suffix_paths(char *path1, char *path2, size_t size, + const char *lo_suffix, const char *hi_suffix, + const char *fmt, va_list ap) +{ + char prefix[PATH_MAX]; + + build_path(prefix, sizeof(prefix), fmt, ap); + + snprintf(path1, size, "%s%s", prefix, lo_suffix); + snprintf(path2, size, "%s%s", prefix, hi_suffix); +} + +static FILE *try_fopen(const char *file, const int lineno, const char *path) +{ + FILE *f = fopen(path, "r"); + + if (f) + return f; + + if (errno == ENOENT) + tst_res_(file, lineno, TCONF, "%s does not exist", path); + else + tst_res_(file, lineno, TBROK | TERRNO, "Failed to open %s", path); + + return NULL; +} + +static void range_check(const char *file, const int lineno, + const char *path, long min, long max) +{ + long val; + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%ld", &val)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path); + return; + } + + if (val < min || val > max) { + tst_res_(file, lineno, TFAIL, + "%s = %ld out of range [%ld, %ld]", path, val, min, max); + return; + } + + tst_res_(file, lineno, TPASS, + "%s = %ld in range [%ld, %ld]", path, val, min, max); +} + +void tst_sysfs_assert_rangell(const char *file, const int lineno, + long min, long max, const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + range_check(file, lineno, path, min, max); +} + +void tst_sysfs_assert_rangell_silent(const char *file, const int lineno, + long min, long max, const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) + return; + + range_check(file, lineno, path, min, max); +} + +static void range_check_f(const char *file, const int lineno, long min, + const char *val_path, const char *max_path) +{ + long max; + + if (access(max_path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", max_path); + return; + } + + if (file_scanf(file, lineno, max_path, "%ld", &max)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", + max_path); + return; + } + + range_check(file, lineno, val_path, min, max); +} + +void tst_sysfs_assert_rangelf(const char *file, const int lineno, + long min, const char *val_suffix, + const char *max_suffix, + const char *fmt, ...) +{ + char val_path[PATH_MAX], max_path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_suffix_paths(val_path, max_path, sizeof(val_path), val_suffix, + max_suffix, fmt, ap); + va_end(ap); + + range_check_f(file, lineno, min, val_path, max_path); +} + +static void bool_check(const char *file, const int lineno, const char *path) +{ + long val; + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%ld", &val)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path); + return; + } + + if (val != 0 && val != 1) { + tst_res_(file, lineno, TFAIL, "%s = %ld is not boolean", path, val); + return; + } + + tst_res_(file, lineno, TPASS, "%s = %ld is boolean", path, val); +} + +void tst_sysfs_assert_bool(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + bool_check(file, lineno, path); +} + +void tst_sysfs_assert_bool_silent(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) + return; + + bool_check(file, lineno, path); +} + +static void pow2_check(const char *file, const int lineno, const char *path) +{ + long val; + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%ld", &val)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path); + return; + } + + if (val <= 0 || (val & (val - 1))) { + tst_res_(file, lineno, TFAIL, + "%s = %ld is not a power of two", path, val); + return; + } + + tst_res_(file, lineno, TPASS, "%s = %ld is a power of two", path, val); +} + +void tst_sysfs_assert_pow2(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + pow2_check(file, lineno, path); +} + +static int is_allowed(const char *const allowed[], const char *val) +{ + const char *const *a; + + for (a = allowed; *a; a++) { + if (!strcmp(*a, val)) + return 1; + } + + return 0; +} + +static void tokens_check(const char *file, const int lineno, + const char *path, const char *const allowed[], + char *sel, size_t sel_size, int sel_expected, + const char *find) +{ + FILE *f; + char *tok = NULL; + size_t tok_size = 0; + ssize_t len; + int nselected = 0; + int ntokens = 0; + int bad = 0; + int found = 0; + + f = try_fopen(file, lineno, path); + if (!f) + return; + + while ((len = getdelim(&tok, &tok_size, ' ', f)) != -1) { + int selected = 0; + const char *name = tok; + + /* Strip the trailing ' ' or '\n' left by getdelim(). */ + while (len > 0 && (tok[len - 1] == ' ' || tok[len - 1] == '\n')) + tok[--len] = '\0'; + + /* Skip empty tokens, e.g. from runs of consecutive spaces. */ + if (len == 0) + continue; + + if (sel_expected && len >= 2 && tok[0] == '[' && tok[len - 1] == ']') { + selected = 1; + tok[len - 1] = '\0'; + name = tok + 1; + } + + ntokens++; + + if (selected) { + nselected++; + if (sel && sel_size) { + strncpy(sel, name, sel_size - 1); + sel[sel_size - 1] = '\0'; + } + } + + if (find && !strcmp(find, name)) + found = 1; + + if (allowed && !is_allowed(allowed, name)) { + tst_res_(file, lineno, TFAIL, + "%s contains unexpected token '%s'", + path, name); + bad = 1; + } + } + + fclose(f); + + if (!ntokens) { + tst_res_(file, lineno, TFAIL, "%s is empty", path); + goto out; + } + + if (sel_expected && nselected != 1) { + tst_res_(file, lineno, TFAIL, + "%s has %d selected tokens, expected exactly 1", + path, nselected); + goto out; + } + + if (bad) + goto out; + + if (find && !found) { + tst_res_(file, lineno, TFAIL, + "%s does not contain token '%s'", path, find); + goto out; + } + + if (sel_expected) { + tst_res_(file, lineno, TPASS, + "%s has valid choices with single selected", path); + } else if (find) { + tst_res_(file, lineno, TPASS, + "%s contains token '%s'", path, find); + } else { + tst_res_(file, lineno, TPASS, + "%s lists only known tokens", path); + } +out: + free(tok); +} + +void tst_sysfs_assert_choice(const char *file, const int lineno, + const char *const allowed[], char *sel, + size_t sel_size, const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + tokens_check(file, lineno, path, allowed, sel, sel_size, 1, NULL); +} + +void tst_sysfs_assert_tokens(const char *file, const int lineno, + const char *const allowed[], const char *find, + const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + tokens_check(file, lineno, path, allowed, NULL, 0, 0, find); +} + +void tst_sysfs_assert_oneof(const char *file, const int lineno, + const char *const allowed[], const char *fmt, ...) +{ + char path[PATH_MAX]; + char val[256]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%255s", val)) { + tst_res_(file, lineno, TFAIL, "Failed to read %s", path); + return; + } + + if (is_allowed(allowed, val)) + tst_res_(file, lineno, TPASS, "%s = '%s' is valid", path, val); + else + tst_res_(file, lineno, TFAIL, + "%s = '%s' is not an expected value", path, val); +} + +static int parse_range_token(char *tok, int *a, int *b) +{ + char *dash; + int ret = 0; + + dash = strchr(tok, '-'); + if (!dash) { + if (tst_parse_int(tok, a, 0, INT_MAX)) + return -1; + + *b = *a; + return 0; + } + + *dash = '\0'; + + if (tst_parse_int(tok, a, 0, INT_MAX) || + tst_parse_int(dash + 1, b, *a, INT_MAX)) { + ret = -1; + } + + *dash = '-'; + + return ret; +} + +static int parse_list(const char *file, const int lineno, const char *path, + unsigned char *map, int *count, int *max_id) +{ + FILE *f; + char *tok = NULL; + size_t tok_size = 0; + ssize_t len; + int total = 0; + int maxid = -1; + + f = try_fopen(file, lineno, path); + if (!f) + return -1; + + while ((len = getdelim(&tok, &tok_size, ',', f)) != -1) { + int a, b, i; + + /* Strip the trailing ',' or '\n' left by getdelim(). */ + while (len > 0 && (tok[len - 1] == ',' || tok[len - 1] == '\n')) + tok[--len] = '\0'; + + /* + * Skip empty tokens, e.g. a lone trailing newline on an + * otherwise empty file (all CPUs online -> empty offline + * cpulist). + */ + if (len == 0) + continue; + + if (parse_range_token(tok, &a, &b)) { + tst_res_(file, lineno, TFAIL, + "%s: invalid list token '%s'", path, tok); + free(tok); + fclose(f); + return -1; + } + + for (i = a; i <= b; i++) { + total++; + if (i > maxid) + maxid = i; + if (map) + map[i / 8] |= 1 << (i % 8); + } + } + + free(tok); + fclose(f); + + if (count) + *count = total; + if (max_id) + *max_id = maxid; + + return 0; +} + +int tst_sysfs_assert_parse_list(const char *file, const int lineno, + int *count, int *max_id, const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + return parse_list(file, lineno, path, NULL, count, max_id); +} + +static unsigned char *read_list_map(const char *file, const int lineno, + const char *path, int *map_bytes) +{ + unsigned char *map; + int max_id, bytes; + + /* First pass: only to find the highest id so we can size the bitmap. */ + if (parse_list(file, lineno, path, NULL, NULL, &max_id)) + return NULL; + + bytes = max_id < 0 ? 1 : max_id / 8 + 1; + map = SAFE_MALLOC(bytes); + memset(map, 0, bytes); + + /* Second pass: fill the bitmap. */ + if (parse_list(file, lineno, path, map, NULL, NULL)) { + free(map); + return NULL; + } + + *map_bytes = bytes; + return map; +} + +void tst_sysfs_assert_list_subset(const char *file, const int lineno, + const char *sub_path, + const char *super_path) +{ + unsigned char *sub = NULL, *super = NULL; + int sub_bytes, super_bytes, i; + + sub = read_list_map(file, lineno, sub_path, &sub_bytes); + if (!sub) + return; + + super = read_list_map(file, lineno, super_path, &super_bytes); + if (!super) { + free(sub); + return; + } + + for (i = 0; i < sub_bytes; i++) { + unsigned char super_byte = i < super_bytes ? super[i] : 0; + + if (sub[i] & ~super_byte) { + tst_res_(file, lineno, TFAIL, + "%s is not a subset of %s", + sub_path, super_path); + free(sub); + free(super); + return; + } + } + + free(sub); + free(super); + + tst_res_(file, lineno, TPASS, + "%s is a subset of %s", sub_path, super_path); +} + +void tst_sysfs_assert_list_contains(const char *file, const int lineno, + int id, const char *fmt, ...) +{ + char path[PATH_MAX]; + unsigned char *map; + int map_bytes; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + map = read_list_map(file, lineno, path, &map_bytes); + if (!map) + return; + + if (id >= 0 && id / 8 < map_bytes && (map[id / 8] & (1 << (id % 8)))) { + tst_res_(file, lineno, TPASS, "%s contains %d", path, id); + } else { + tst_res_(file, lineno, TFAIL, "%s does not contain %d", path, + id); + } + + free(map); +} + +void tst_sysfs_assert_cmp(const char *file, const int lineno, + const char *path1, enum tst_sysfs_cmp op, + const char *path2) +{ + long val1, val2; + int res; + const char *opstr; + + if (access(path1, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path1); + return; + } + + if (access(path2, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path2); + return; + } + + if (file_scanf(file, lineno, path1, "%ld", &val1)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path1); + return; + } + + if (file_scanf(file, lineno, path2, "%ld", &val2)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path2); + return; + } + + switch (op) { + case TST_SYSFS_CMP_EQ: + res = val1 == val2; + opstr = "=="; + break; + case TST_SYSFS_CMP_LT: + res = val1 < val2; + opstr = "<"; + break; + case TST_SYSFS_CMP_LE: + res = val1 <= val2; + opstr = "<="; + break; + default: + tst_res_(file, lineno, TBROK, "Invalid comparison operator %d", op); + return; + } + + tst_res_(file, lineno, res ? TPASS : TFAIL, + "%s (%ld) %s %s (%ld)", + path1, val1, opstr, path2, val2); +} + +void tst_sysfs_assert_cmp_silent(const char *file, const int lineno, + const char *path1, enum tst_sysfs_cmp op, + const char *path2) +{ + if (access(path1, F_OK) || access(path2, F_OK)) + return; + + tst_sysfs_assert_cmp(file, lineno, path1, op, path2); +} + +void tst_sysfs_assert_cmp_suffix(const char *file, const int lineno, + enum tst_sysfs_cmp op, const char *lo_suffix, + const char *hi_suffix, const char *fmt, ...) +{ + char path1[PATH_MAX], path2[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_suffix_paths(path1, path2, sizeof(path1), lo_suffix, hi_suffix, + fmt, ap); + va_end(ap); + + tst_sysfs_assert_cmp(file, lineno, path1, op, path2); +} + +void tst_sysfs_assert_cmp_suffix_silent(const char *file, const int lineno, + enum tst_sysfs_cmp op, + const char *lo_suffix, + const char *hi_suffix, + const char *fmt, ...) +{ + char path1[PATH_MAX], path2[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_suffix_paths(path1, path2, sizeof(path1), lo_suffix, hi_suffix, + fmt, ap); + va_end(ap); + + tst_sysfs_assert_cmp_silent(file, lineno, path1, op, path2); +} + +int tst_sysfs_exists(const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + return !access(path, F_OK); +} + +int tst_sysfs_read_str(const char *file, const int lineno, char *buf, + size_t buf_size, const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) + return 0; + + safe_file_read_str(file, lineno, path, buf, buf_size); + buf[strcspn(buf, "\n")] = '\0'; + + return 1; +} + +void tst_sysfs_exp_eq_li(const char *file, const int lineno, long val, + const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list ap; + long read_val; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%ld", &read_val)) { + tst_res_(file, lineno, TFAIL, "%s does not contain a number", path); + return; + } + + tst_res_(file, lineno, read_val == val ? TPASS : TFAIL, + "%s (%ld) == %ld", path, read_val, val); +} + +void tst_sysfs_exp_eq_str(const char *file, const int lineno, const char *val, + const char *fmt, ...) +{ + char path[PATH_MAX]; + char read_val[256]; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + if (access(path, F_OK)) { + tst_res_(file, lineno, TCONF, "%s does not exist", path); + return; + } + + if (file_scanf(file, lineno, path, "%255s", read_val)) { + tst_res_(file, lineno, TFAIL, "Failed to read %s", path); + return; + } + + tst_res_(file, lineno, !strcmp(read_val, val) ? TPASS : TFAIL, + "%s ('%s') == '%s'", path, read_val, val); +} + +long long tst_sysfs_read_lli(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + long long val; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + safe_file_scanf(file, lineno, NULL, path, "%lld", &val); + + tst_res(TDEBUG, "Read '%s' = %lli", path, val); + + return val; +} + +long tst_sysfs_read_li(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + long val; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + safe_file_scanf(file, lineno, NULL, path, "%ld", &val); + + tst_res(TDEBUG, "Read '%s' = %li", path, val); + + return val; +} + +unsigned long tst_sysfs_read_lx(const char *file, const int lineno, + const char *fmt, ...) +{ + char path[PATH_MAX]; + unsigned long val; + va_list ap; + + va_start(ap, fmt); + build_path(path, sizeof(path), fmt, ap); + va_end(ap); + + safe_file_scanf(file, lineno, NULL, path, "%lx", &val); + + tst_res(TDEBUG, "Read '%s' = 0x%lx", path, val); + + return val; +} -- 2.54.0 -- Mailing list info: https://lists.linux.it/listinfo/ltp