All of lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [PATCH v1 00/31] Add sysfs sanity tests
@ 2026-08-18 14:12 Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 01/31] lib: Add tst_sysfs_assert Cyril Hrubis
                   ` (30 more replies)
  0 siblings, 31 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

This is a first bach of the sysfs sanity tests I've been working on
recently.

The main goal of these tests is to make sure that these files contains
expected data, e.g. files that are supposes to be boolean are 0 or 1,
numeric files contain numbers, etc.

I've tried to make the code of the tests as clear as possible. Hence the
first addition in this patchset is a library that implements common
assertions for sysfs files to be done in a single line of a code.

Majority of these tests are just reading the files and checking that the
content is sane. The exceptions are loop device, veth, tap tests that
cerate virtual devices, which attributes can be changed freely.

Cyril Hrubis (31):
  lib: Add tst_sysfs_assert
  testcases: sysfs: Add sys_power01
  testcases: sysfs: Add sys_kernel01
  testcases: sysfs: Add sys_clocksource01
  testcases: sysfs: Add sys_node01
  testcases: sysfs: Add sys_cpu_topology01
  testcases: sysfs: Add sys_cpu_topology02
  testcases: sysfs: Add sys_cpu_vulnerabilities01
  testcases: sysfs: Add sys_cpu_smt01
  testcases: sysfs: Add sys_cpu_cache01
  testcases: sysfs: Add sys_clockevents01
  testcases: sysfs: Add sys_ata01
  testcases: sysfs: Add sys_bdi01
  testcases: sysfs: sys_hwmon01
  testcases: sysfs: sys_leds01
  testcases: sysfs: Add sys_wakeup01
  testcases: sysfs: Add sys_rtc01
  testcases: sysfs: Add sys_thermal01
  tst_netdevice: Add two more helper macros
  testcases: sysfs: Add sys_net01
  testcases: sysfs: Add sys_net02
  testcases: sysfs: Add sys_net03
  testcases: sysfs: Add sys_net04
  testcases: sysfs: Add sys_block_loop01
  testcases: sysfs: Add sys_block_queue01
  testcases: sysfs: Add sys_block_size01
  testcases: sysfs: Add sys_hugepages01
  testcases: sysfs: Add sys_hugepages02
  testcases: sysfs: Add sys_ksm01
  testcases: sysfs: Add sys_swap01
  testcases: sysfs: Add sys_thp01

 doc/developers/api_c_tests.rst                |   5 +
 include/tst_netdevice.h                       |  20 +
 include/tst_sysfs_assert.h                    | 588 +++++++++++++
 lib/tst_netdevice.c                           |  90 ++
 lib/tst_sysfs_assert.c                        | 797 ++++++++++++++++++
 runtest/sysfs                                 |  29 +
 testcases/kernel/sysfs/Makefile               |   7 +
 testcases/kernel/sysfs/block/.gitignore       |   3 +
 testcases/kernel/sysfs/block/Makefile         |   7 +
 .../kernel/sysfs/block/sys_block_loop01.c     | 320 +++++++
 .../kernel/sysfs/block/sys_block_queue01.c    | 106 +++
 .../kernel/sysfs/block/sys_block_size01.c     | 102 +++
 testcases/kernel/sysfs/class/Makefile         |   7 +
 .../kernel/sysfs/class/ata_device/.gitignore  |   1 +
 .../kernel/sysfs/class/ata_device/Makefile    |   7 +
 .../kernel/sysfs/class/ata_device/sys_ata01.c |  93 ++
 testcases/kernel/sysfs/class/bdi/.gitignore   |   1 +
 testcases/kernel/sysfs/class/bdi/Makefile     |   7 +
 testcases/kernel/sysfs/class/bdi/sys_bdi01.c  |  85 ++
 testcases/kernel/sysfs/class/hwmon/.gitignore |   1 +
 testcases/kernel/sysfs/class/hwmon/Makefile   |   7 +
 .../kernel/sysfs/class/hwmon/sys_hwmon01.c    | 196 +++++
 testcases/kernel/sysfs/class/leds/.gitignore  |   1 +
 testcases/kernel/sysfs/class/leds/Makefile    |   7 +
 .../kernel/sysfs/class/leds/sys_leds01.c      |  67 ++
 testcases/kernel/sysfs/class/net/.gitignore   |   4 +
 testcases/kernel/sysfs/class/net/Makefile     |   7 +
 testcases/kernel/sysfs/class/net/sys_net01.c  | 123 +++
 testcases/kernel/sysfs/class/net/sys_net02.c  | 179 ++++
 testcases/kernel/sysfs/class/net/sys_net03.c  | 144 ++++
 testcases/kernel/sysfs/class/net/sys_net04.c  | 211 +++++
 .../kernel/sysfs/class/net/sys_net_common.h   |  80 ++
 testcases/kernel/sysfs/class/rtc/.gitignore   |   1 +
 testcases/kernel/sysfs/class/rtc/Makefile     |   7 +
 testcases/kernel/sysfs/class/rtc/sys_rtc01.c  | 200 +++++
 .../kernel/sysfs/class/thermal/.gitignore     |   1 +
 testcases/kernel/sysfs/class/thermal/Makefile |   7 +
 .../sysfs/class/thermal/sys_thermal01.c       |  70 ++
 .../kernel/sysfs/class/wakeup/.gitignore      |   1 +
 testcases/kernel/sysfs/class/wakeup/Makefile  |   7 +
 .../kernel/sysfs/class/wakeup/sys_wakeup01.c  |  93 ++
 testcases/kernel/sysfs/devices/Makefile       |   7 +
 .../kernel/sysfs/devices/system/Makefile      |   7 +
 .../devices/system/clockevents/.gitignore     |   1 +
 .../sysfs/devices/system/clockevents/Makefile |   7 +
 .../system/clockevents/sys_clockevents01.c    |  88 ++
 .../devices/system/clocksource/.gitignore     |   1 +
 .../sysfs/devices/system/clocksource/Makefile |   7 +
 .../system/clocksource/sys_clocksource01.c    |  67 ++
 .../sysfs/devices/system/cpu/.gitignore       |   5 +
 .../kernel/sysfs/devices/system/cpu/Makefile  |   7 +
 .../devices/system/cpu/sys_cpu_cache01.c      | 100 +++
 .../sysfs/devices/system/cpu/sys_cpu_smt01.c  |  65 ++
 .../devices/system/cpu/sys_cpu_topology01.c   | 103 +++
 .../devices/system/cpu/sys_cpu_topology02.c   |  98 +++
 .../system/cpu/sys_cpu_vulnerabilities01.c    |  85 ++
 .../sysfs/devices/system/node/.gitignore      |   1 +
 .../kernel/sysfs/devices/system/node/Makefile |   7 +
 .../sysfs/devices/system/node/sys_node01.c    | 112 +++
 testcases/kernel/sysfs/kernel/.gitignore      |   1 +
 testcases/kernel/sysfs/kernel/Makefile        |   7 +
 testcases/kernel/sysfs/kernel/mm/Makefile     |   7 +
 .../sysfs/kernel/mm/hugepages/.gitignore      |   2 +
 .../kernel/sysfs/kernel/mm/hugepages/Makefile |   7 +
 .../kernel/mm/hugepages/sys_hugepages01.c     | 133 +++
 .../kernel/mm/hugepages/sys_hugepages02.c     |  84 ++
 .../kernel/sysfs/kernel/mm/ksm/.gitignore     |   1 +
 testcases/kernel/sysfs/kernel/mm/ksm/Makefile |   7 +
 .../kernel/sysfs/kernel/mm/ksm/sys_ksm01.c    |  64 ++
 .../kernel/sysfs/kernel/mm/swap/.gitignore    |   1 +
 .../kernel/sysfs/kernel/mm/swap/Makefile      |   7 +
 .../sysfs/kernel/mm/swap/sys_mm_swap01.c      |  32 +
 .../kernel/mm/transparent_hugepage/.gitignore |   1 +
 .../kernel/mm/transparent_hugepage/Makefile   |   7 +
 .../mm/transparent_hugepage/sys_thp01.c       |  86 ++
 testcases/kernel/sysfs/kernel/sys_kernel01.c  |  46 +
 testcases/kernel/sysfs/power/.gitignore       |   1 +
 testcases/kernel/sysfs/power/Makefile         |   7 +
 testcases/kernel/sysfs/power/sys_power01.c    |  66 ++
 79 files changed, 5024 insertions(+)
 create mode 100644 include/tst_sysfs_assert.h
 create mode 100644 lib/tst_sysfs_assert.c
 create mode 100644 runtest/sysfs
 create mode 100644 testcases/kernel/sysfs/Makefile
 create mode 100644 testcases/kernel/sysfs/block/.gitignore
 create mode 100644 testcases/kernel/sysfs/block/Makefile
 create mode 100644 testcases/kernel/sysfs/block/sys_block_loop01.c
 create mode 100644 testcases/kernel/sysfs/block/sys_block_queue01.c
 create mode 100644 testcases/kernel/sysfs/block/sys_block_size01.c
 create mode 100644 testcases/kernel/sysfs/class/Makefile
 create mode 100644 testcases/kernel/sysfs/class/ata_device/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/ata_device/Makefile
 create mode 100644 testcases/kernel/sysfs/class/ata_device/sys_ata01.c
 create mode 100644 testcases/kernel/sysfs/class/bdi/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/bdi/Makefile
 create mode 100644 testcases/kernel/sysfs/class/bdi/sys_bdi01.c
 create mode 100644 testcases/kernel/sysfs/class/hwmon/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/hwmon/Makefile
 create mode 100644 testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
 create mode 100644 testcases/kernel/sysfs/class/leds/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/leds/Makefile
 create mode 100644 testcases/kernel/sysfs/class/leds/sys_leds01.c
 create mode 100644 testcases/kernel/sysfs/class/net/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/net/Makefile
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net01.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net02.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net03.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net04.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net_common.h
 create mode 100644 testcases/kernel/sysfs/class/rtc/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/rtc/Makefile
 create mode 100644 testcases/kernel/sysfs/class/rtc/sys_rtc01.c
 create mode 100644 testcases/kernel/sysfs/class/thermal/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/thermal/Makefile
 create mode 100644 testcases/kernel/sysfs/class/thermal/sys_thermal01.c
 create mode 100644 testcases/kernel/sysfs/class/wakeup/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/wakeup/Makefile
 create mode 100644 testcases/kernel/sysfs/class/wakeup/sys_wakeup01.c
 create mode 100644 testcases/kernel/sysfs/devices/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/sys_clockevents01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/sys_clocksource01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_cache01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_smt01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology02.c
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_vulnerabilities01.c
 create mode 100644 testcases/kernel/sysfs/devices/system/node/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/node/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/node/sys_node01.c
 create mode 100644 testcases/kernel/sysfs/kernel/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages01.c
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages02.c
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/sys_ksm01.c
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp01.c
 create mode 100644 testcases/kernel/sysfs/kernel/sys_kernel01.c
 create mode 100644 testcases/kernel/sysfs/power/.gitignore
 create mode 100644 testcases/kernel/sysfs/power/Makefile
 create mode 100644 testcases/kernel/sysfs/power/sys_power01.c

-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 01/31] lib: Add tst_sysfs_assert
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 02/31] testcases: sysfs: Add sys_power01 Cyril Hrubis
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

Adds helpers for a sysfs testscases.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 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 <chrubis@suse.cz>
+ */
+
+#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 <chrubis@suse.cz>
+ */
+
+#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

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 02/31] testcases: sysfs: Add sys_power01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 01/31] lib: Add tst_sysfs_assert Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 03/31] testcases: sysfs: Add sys_kernel01 Cyril Hrubis
                   ` (28 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for sysfs/power/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                              |  1 +
 testcases/kernel/sysfs/Makefile            |  7 +++
 testcases/kernel/sysfs/power/.gitignore    |  1 +
 testcases/kernel/sysfs/power/Makefile      |  7 +++
 testcases/kernel/sysfs/power/sys_power01.c | 66 ++++++++++++++++++++++
 5 files changed, 82 insertions(+)
 create mode 100644 runtest/sysfs
 create mode 100644 testcases/kernel/sysfs/Makefile
 create mode 100644 testcases/kernel/sysfs/power/.gitignore
 create mode 100644 testcases/kernel/sysfs/power/Makefile
 create mode 100644 testcases/kernel/sysfs/power/sys_power01.c

diff --git a/runtest/sysfs b/runtest/sysfs
new file mode 100644
index 000000000..e679cacb1
--- /dev/null
+++ b/runtest/sysfs
@@ -0,0 +1 @@
+sys_power01 sys_power01
diff --git a/testcases/kernel/sysfs/Makefile b/testcases/kernel/sysfs/Makefile
new file mode 100644
index 000000000..654cda49e
--- /dev/null
+++ b/testcases/kernel/sysfs/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/power/.gitignore b/testcases/kernel/sysfs/power/.gitignore
new file mode 100644
index 000000000..835842cd0
--- /dev/null
+++ b/testcases/kernel/sysfs/power/.gitignore
@@ -0,0 +1 @@
+/sys_power01
diff --git a/testcases/kernel/sysfs/power/Makefile b/testcases/kernel/sysfs/power/Makefile
new file mode 100644
index 000000000..66e633f40
--- /dev/null
+++ b/testcases/kernel/sysfs/power/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/power/sys_power01.c b/testcases/kernel/sysfs/power/sys_power01.c
new file mode 100644
index 000000000..3c78719e5
--- /dev/null
+++ b/testcases/kernel/sysfs/power/sys_power01.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the power management attributes exported under
+ * /sys/power/.
+ *
+ * The test verifies that:
+ *
+ * - pm_async, pm_debug_messages, pm_print_times and sync_on_suspend are
+ *   booleans
+ * - pm_freeze_timeout and wakeup_count are non-negative
+ * - disk is a bracketed-choice file selecting one of platform, shutdown,
+ *   reboot, suspend, test_resume or firmware
+ * - mem_sleep is a bracketed-choice file selecting one of s2idle, shallow or
+ *   deep
+ * - state lists only known sleep states (freeze, standby, mem, disk, on)
+ *
+ * Unlike disk and mem_sleep, state never marks a "current" value with
+ * brackets, it merely enumerates the sleep states the kernel supports, so it
+ * is validated as a plain token set rather than a bracketed choice.
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present.
+ */
+
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define POWER "/sys/power"
+
+static const char *const disk_allowed[] = {
+	"platform", "shutdown", "reboot", "suspend", "test_resume",
+	"firmware", NULL
+};
+
+static const char *const mem_sleep_allowed[] = {
+	"s2idle", "shallow", "deep", NULL
+};
+
+static const char *const state_allowed[] = {
+	"freeze", "standby", "mem", "disk", "on", NULL
+};
+
+static void do_test(void)
+{
+	TST_SYSFS_ASSERT_BOOL(POWER "/pm_async");
+	TST_SYSFS_ASSERT_BOOL(POWER "/pm_debug_messages");
+	TST_SYSFS_ASSERT_BOOL(POWER "/pm_print_times");
+	TST_SYSFS_ASSERT_BOOL(POWER "/sync_on_suspend");
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, POWER "/pm_freeze_timeout");
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, POWER "/wakeup_count");
+
+	TST_SYSFS_ASSERT_CHOICE(disk_allowed, NULL, 0, POWER "/disk");
+	TST_SYSFS_ASSERT_CHOICE(mem_sleep_allowed, NULL, 0, POWER "/mem_sleep");
+
+	TST_SYSFS_ASSERT_TOKENS(state_allowed, NULL, POWER "/state");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 03/31] testcases: sysfs: Add sys_kernel01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 01/31] lib: Add tst_sysfs_assert Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 02/31] testcases: sysfs: Add sys_power01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:54   ` [LTP] lib: Add tst_sysfs_assert linuxtestproject.agent
  2026-08-18 14:12 ` [LTP] [PATCH v1 04/31] testcases: sysfs: Add sys_clocksource01 Cyril Hrubis
                   ` (27 subsequent siblings)
  30 siblings, 1 reply; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |  1 +
 testcases/kernel/sysfs/kernel/.gitignore     |  1 +
 testcases/kernel/sysfs/kernel/Makefile       |  7 +++
 testcases/kernel/sysfs/kernel/sys_kernel01.c | 46 ++++++++++++++++++++
 4 files changed, 55 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/sys_kernel01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index e679cacb1..142b4a290 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -1 +1,2 @@
 sys_power01 sys_power01
+sys_kernel01 sys_kernel01
diff --git a/testcases/kernel/sysfs/kernel/.gitignore b/testcases/kernel/sysfs/kernel/.gitignore
new file mode 100644
index 000000000..d11d43ce2
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/.gitignore
@@ -0,0 +1 @@
+/sys_kernel01
diff --git a/testcases/kernel/sysfs/kernel/Makefile b/testcases/kernel/sysfs/kernel/Makefile
new file mode 100644
index 000000000..d48598a66
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/kernel/sys_kernel01.c b/testcases/kernel/sysfs/kernel/sys_kernel01.c
new file mode 100644
index 000000000..b5efbe2e1
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/sys_kernel01.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for assorted scalar attributes exported directly under
+ * /sys/kernel/.
+ *
+ * The test verifies that:
+ *
+ * - address_bits is either 32 or 64
+ * - cpu_byteorder is "little" or "big" and matches the host byte order
+ * - rcu_expedited and rcu_normal are booleans (0 or 1)
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present, since these files were added over several kernel releases.
+ */
+
+#include <endian.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_KERNEL "/sys/kernel"
+
+static const char *const address_bits_allowed[] = {
+	"32", "64", NULL
+};
+
+static void run(void)
+{
+	TST_SYSFS_ASSERT_ONEOF(address_bits_allowed, SYS_KERNEL "/address_bits");
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	TST_SYSFS_EXP_EQ_STR("little", SYS_KERNEL "/cpu_byteorder");
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	TST_SYSFS_EXP_EQ_STR("big", SYS_KERNEL "/cpu_byteorder");
+#endif
+
+	TST_SYSFS_ASSERT_BOOL(SYS_KERNEL "/rcu_expedited");
+	TST_SYSFS_ASSERT_BOOL(SYS_KERNEL "/rcu_normal");
+}
+
+static struct tst_test test = {
+	.test_all = run,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 04/31] testcases: sysfs: Add sys_clocksource01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (2 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 03/31] testcases: sysfs: Add sys_kernel01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 05/31] testcases: sysfs: Add sys_node01 Cyril Hrubis
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/clocksource/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 testcases/kernel/sysfs/devices/Makefile       |  7 ++
 .../kernel/sysfs/devices/system/Makefile      |  7 ++
 .../devices/system/clocksource/.gitignore     |  1 +
 .../sysfs/devices/system/clocksource/Makefile |  7 ++
 .../system/clocksource/sys_clocksource01.c    | 67 +++++++++++++++++++
 6 files changed, 90 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clocksource/sys_clocksource01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 142b4a290..7c014a57e 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -1,2 +1,3 @@
 sys_power01 sys_power01
 sys_kernel01 sys_kernel01
+sys_clocksource01 sys_clocksource01
diff --git a/testcases/kernel/sysfs/devices/Makefile b/testcases/kernel/sysfs/devices/Makefile
new file mode 100644
index 000000000..5c8e7a249
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/Makefile b/testcases/kernel/sysfs/devices/system/Makefile
new file mode 100644
index 000000000..9bc26552c
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/clocksource/.gitignore b/testcases/kernel/sysfs/devices/system/clocksource/.gitignore
new file mode 100644
index 000000000..a04f05977
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clocksource/.gitignore
@@ -0,0 +1 @@
+/sys_clocksource01
diff --git a/testcases/kernel/sysfs/devices/system/clocksource/Makefile b/testcases/kernel/sysfs/devices/system/clocksource/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clocksource/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/clocksource/sys_clocksource01.c b/testcases/kernel/sysfs/devices/system/clocksource/sys_clocksource01.c
new file mode 100644
index 000000000..534caddd8
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clocksource/sys_clocksource01.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the clocksource attributes exported under
+ * /sys/devices/system/clocksource/clocksourceN/.
+ *
+ * available_clocksource lists all clocksources the kernel can switch to,
+ * while current_clocksource names the one that is active. Unlike the
+ * bracketed-choice files elsewhere in sysfs, the currently selected
+ * clocksource is not marked inside available_clocksource, so the two files
+ * have to be cross-checked explicitly. For every clocksourceN directory the
+ * test verifies that:
+ *
+ * - current_clocksource is non-empty
+ * - current_clocksource is one of the tokens listed in available_clocksource
+ *
+ * The test skips with TCONF when no clocksource directory is present.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define CLOCKSOURCE "/sys/devices/system/clocksource"
+
+static void check_clocksource(const char *name)
+{
+	char current[64] = "";
+
+	tst_res(TINFO, "Checking %s", name);
+
+	TST_SYSFS_READ_STR(current, sizeof(current),
+			   CLOCKSOURCE "/%s/current_clocksource", name);
+
+	TST_SYSFS_ASSERT_TOKENS(NULL, current,
+				CLOCKSOURCE "/%s/available_clocksource", name);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(CLOCKSOURCE);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "clocksource", 11))
+			continue;
+
+		found = 1;
+		check_clocksource(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No clocksource directory found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 05/31] testcases: sysfs: Add sys_node01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (3 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 04/31] testcases: sysfs: Add sys_clocksource01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 06/31] testcases: sysfs: Add sys_cpu_topology01 Cyril Hrubis
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for a /sys/devices/system/node/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 .../sysfs/devices/system/node/.gitignore      |   1 +
 .../kernel/sysfs/devices/system/node/Makefile |   7 ++
 .../sysfs/devices/system/node/sys_node01.c    | 112 ++++++++++++++++++
 4 files changed, 121 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/node/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/node/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/node/sys_node01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 7c014a57e..347bd78c1 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -1,3 +1,4 @@
 sys_power01 sys_power01
 sys_kernel01 sys_kernel01
 sys_clocksource01 sys_clocksource01
+sys_node01 sys_node01
diff --git a/testcases/kernel/sysfs/devices/system/node/.gitignore b/testcases/kernel/sysfs/devices/system/node/.gitignore
new file mode 100644
index 000000000..42a442639
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/.gitignore
@@ -0,0 +1 @@
+/sys_node01
diff --git a/testcases/kernel/sysfs/devices/system/node/Makefile b/testcases/kernel/sysfs/devices/system/node/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/node/sys_node01.c b/testcases/kernel/sysfs/devices/system/node/sys_node01.c
new file mode 100644
index 000000000..2c2dca30b
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/sys_node01.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the NUMA node attributes exported under
+ * /sys/devices/system/node/.
+ *
+ * The kernel exports cpulist-style files describing the node state:
+ *
+ * - possible - node ids that could possibly be online
+ * - online   - node ids that are currently online
+ *
+ * as well as a per-node memory summary in nodeN/meminfo.
+ *
+ * The test verifies that:
+ *
+ * - online is a subset of possible
+ * - for each online node MemUsed == MemTotal - MemFree
+ * - the sum of all per-node MemTotal values matches MemTotal in /proc/meminfo
+ *
+ * All checks skip gracefully with TCONF on systems without NUMA sysfs.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_NODE "/sys/devices/system/node"
+
+static long read_node_meminfo(int nid, const char *item)
+{
+	char path[PATH_MAX];
+	char fmt[128];
+	long val;
+
+	snprintf(path, sizeof(path), SYS_NODE "/node%d/meminfo", nid);
+	snprintf(fmt, sizeof(fmt), "Node %d %s: %%ld", nid, item);
+
+	SAFE_FILE_LINES_SCANF(path, fmt, &val);
+
+	return val;
+}
+
+static void check_node_mem(int nid, long *sum_total)
+{
+	long total, free, used;
+
+	total = read_node_meminfo(nid, "MemTotal");
+	free = read_node_meminfo(nid, "MemFree");
+	used = read_node_meminfo(nid, "MemUsed");
+
+	*sum_total += total;
+
+	if (used == total - free) {
+		tst_res(TPASS,
+			"node%d: MemUsed (%ld) == MemTotal (%ld) - MemFree (%ld)",
+			nid, used, total, free);
+	} else {
+		tst_res(TFAIL,
+			"node%d: MemUsed (%ld) != MemTotal (%ld) - MemFree (%ld)",
+			nid, used, total, free);
+	}
+}
+
+static void check_sum_memtotal(long sum_total)
+{
+	long global_total;
+
+	SAFE_FILE_LINES_SCANF("/proc/meminfo", "MemTotal: %ld", &global_total);
+
+	if (sum_total == global_total) {
+		tst_res(TPASS,
+			"sum of node MemTotal (%ld kB) matches /proc/meminfo MemTotal (%ld kB)",
+			sum_total, global_total);
+	} else {
+		tst_res(TFAIL,
+			"sum of node MemTotal (%ld kB) differs from /proc/meminfo MemTotal (%ld kB)",
+			sum_total, global_total);
+	}
+}
+
+static void run(void)
+{
+	int count, max_id, nid;
+	long sum_total = 0;
+
+	if (access(SYS_NODE "/online", F_OK)) {
+		tst_res(TCONF, SYS_NODE " is not available");
+		return;
+	}
+
+	TST_SYSFS_ASSERT_LIST_SUBSET(SYS_NODE "/online", SYS_NODE "/possible");
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_NODE "/online"))
+		return;
+
+	for (nid = 0; nid <= max_id; nid++) {
+		if (!tst_sysfs_exists(SYS_NODE "/node%d", nid))
+			continue;
+
+		check_node_mem(nid, &sum_total);
+	}
+
+	check_sum_memtotal(sum_total);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 06/31] testcases: sysfs: Add sys_cpu_topology01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (4 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 05/31] testcases: sysfs: Add sys_node01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 07/31] testcases: sysfs: Add sys_cpu_topology02 Cyril Hrubis
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/cpu/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 .../sysfs/devices/system/cpu/.gitignore       |   1 +
 .../kernel/sysfs/devices/system/cpu/Makefile  |   7 ++
 .../devices/system/cpu/sys_cpu_topology01.c   | 103 ++++++++++++++++++
 4 files changed, 112 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 347bd78c1..4fc8f1eb2 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -2,3 +2,4 @@ sys_power01 sys_power01
 sys_kernel01 sys_kernel01
 sys_clocksource01 sys_clocksource01
 sys_node01 sys_node01
+sys_cpu_topology01 sys_cpu_topology01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/.gitignore b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
new file mode 100644
index 000000000..0615aff77
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
@@ -0,0 +1 @@
+/sys_cpu_topology01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/Makefile b/testcases/kernel/sysfs/devices/system/cpu/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology01.c b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology01.c
new file mode 100644
index 000000000..d2aefa85e
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology01.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the CPU topology attributes exported under
+ * /sys/devices/system/cpu/.
+ *
+ * The kernel exports several cpulist files describing the state of the CPUs:
+ *
+ * - possible - CPUs that could possibly be brought online during this boot
+ * - present  - CPUs that are populated (physically present)
+ * - online   - CPUs that are currently online and usable
+ * - offline  - CPUs that are known to the system but currently offline
+ *
+ * These sets are nested, so the test verifies that:
+ *
+ * - online is a subset of present
+ * - present is a subset of possible
+ * - offline is a subset of possible
+ * - kernel_max is greater than or equal to the highest possible CPU id
+ *
+ * The number of online CPUs is also cross-checked against /proc/stat, which
+ * contains one "cpuN" line for each online CPU.
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_CPU "/sys/devices/system/cpu"
+
+static int count_proc_stat_cpus(void)
+{
+	FILE *f;
+	char line[4096];
+	int cpus = 0;
+
+	f = SAFE_FOPEN("/proc/stat", "r");
+
+	while (fgets(line, sizeof(line), f)) {
+		if (!strncmp(line, "cpu", 3) && isdigit(line[3]))
+			cpus++;
+	}
+
+	SAFE_FCLOSE(f);
+
+	return cpus;
+}
+
+static void check_online_proc_stat(void)
+{
+	int count, max_id;
+	int stat_cpus;
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_CPU "/online"))
+		return;
+
+	stat_cpus = count_proc_stat_cpus();
+
+	if (count == stat_cpus) {
+		tst_res(TPASS,
+			SYS_CPU "/online count (%d) == /proc/stat cpu lines (%d)",
+			count, stat_cpus);
+	} else {
+		tst_res(TFAIL,
+			SYS_CPU "/online count (%d) != /proc/stat cpu lines (%d)",
+			count, stat_cpus);
+	}
+}
+
+static void check_kernel_max(void)
+{
+	int count, max_id;
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_CPU "/possible"))
+		return;
+
+	TST_SYSFS_ASSERT_RANGELL(max_id, LONG_MAX, SYS_CPU "/kernel_max");
+}
+
+static void run(void)
+{
+	TST_SYSFS_ASSERT_LIST_SUBSET(SYS_CPU "/online", SYS_CPU "/present");
+	TST_SYSFS_ASSERT_LIST_SUBSET(SYS_CPU "/present", SYS_CPU "/possible");
+	TST_SYSFS_ASSERT_LIST_SUBSET(SYS_CPU "/offline", SYS_CPU "/possible");
+
+	check_kernel_max();
+
+	check_online_proc_stat();
+}
+
+static struct tst_test test = {
+	.test_all = run,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 07/31] testcases: sysfs: Add sys_cpu_topology02
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (5 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 06/31] testcases: sysfs: Add sys_cpu_topology01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 08/31] testcases: sysfs: Add sys_cpu_vulnerabilities01 Cyril Hrubis
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/cpu/cpu*/topology/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../sysfs/devices/system/cpu/.gitignore       |  1 +
 .../devices/system/cpu/sys_cpu_topology02.c   | 98 +++++++++++++++++++
 3 files changed, 100 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology02.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 4fc8f1eb2..b1daab68f 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -3,3 +3,4 @@ sys_kernel01 sys_kernel01
 sys_clocksource01 sys_clocksource01
 sys_node01 sys_node01
 sys_cpu_topology01 sys_cpu_topology01
+sys_cpu_topology02 sys_cpu_topology02
diff --git a/testcases/kernel/sysfs/devices/system/cpu/.gitignore b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
index 0615aff77..a688a9b50 100644
--- a/testcases/kernel/sysfs/devices/system/cpu/.gitignore
+++ b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
@@ -1 +1,2 @@
 /sys_cpu_topology01
+/sys_cpu_topology02
diff --git a/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology02.c b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology02.c
new file mode 100644
index 000000000..00f7c6af9
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_topology02.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the per-CPU topology attributes exported under
+ * /sys/devices/system/cpu/cpuN/topology/.
+ *
+ * The topology masks describe how a CPU relates to the others. They are nested
+ * from the smallest (SMT threads) to the largest (package) group, so for every
+ * online CPU the test verifies that:
+ *
+ * - thread_siblings_list is a subset of core_cpus_list
+ * - core_cpus_list is a subset of package_cpus_list
+ * - package_cpus_list is a subset of the online CPUs
+ * - the CPU itself is contained in its own thread_siblings_list
+ * - core_id and physical_package_id are in [0, highest possible CPU id],
+ *   since there can be at most as many cores/packages as possible CPUs
+ *   (every core has at least one thread, every package at least one core)
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present, as the exact set of topology files differs between kernel versions
+ * and architectures.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_CPU "/sys/devices/system/cpu"
+
+static void check_self_in_threads(int cpu)
+{
+	TST_SYSFS_ASSERT_LIST_CONTAINS(cpu,
+		SYS_CPU "/cpu%d/topology/thread_siblings_list", cpu);
+}
+
+static void check_cpu_topology(int cpu, int poss_max_id)
+{
+	char sub[PATH_MAX], super[PATH_MAX];
+
+	if (access(SYS_CPU, F_OK))
+		return;
+
+	if (!tst_sysfs_exists(SYS_CPU "/cpu%d/topology/core_id", cpu)) {
+		tst_res(TCONF, "cpu%d has no topology directory", cpu);
+		return;
+	}
+
+	tst_res(TINFO, "Checking cpu%d topology", cpu);
+
+	TST_SYSFS_ASSERT_RANGELL(0, poss_max_id,
+			       SYS_CPU "/cpu%d/topology/core_id", cpu);
+
+	TST_SYSFS_ASSERT_RANGELL(0, poss_max_id,
+			       SYS_CPU "/cpu%d/topology/physical_package_id",
+			       cpu);
+
+	snprintf(sub, sizeof(sub),
+		 SYS_CPU "/cpu%d/topology/thread_siblings_list", cpu);
+	snprintf(super, sizeof(super),
+		 SYS_CPU "/cpu%d/topology/core_cpus_list", cpu);
+	TST_SYSFS_ASSERT_LIST_SUBSET(sub, super);
+
+	snprintf(sub, sizeof(sub),
+		 SYS_CPU "/cpu%d/topology/core_cpus_list", cpu);
+	snprintf(super, sizeof(super),
+		 SYS_CPU "/cpu%d/topology/package_cpus_list", cpu);
+	TST_SYSFS_ASSERT_LIST_SUBSET(sub, super);
+
+	snprintf(sub, sizeof(sub),
+		 SYS_CPU "/cpu%d/topology/package_cpus_list", cpu);
+	TST_SYSFS_ASSERT_LIST_SUBSET(sub, SYS_CPU "/online");
+
+	check_self_in_threads(cpu);
+}
+
+static void do_test(void)
+{
+	int count, max_id, poss_count, poss_max_id, cpu;
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_CPU "/online"))
+		return;
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&poss_count, &poss_max_id,
+					SYS_CPU "/possible"))
+		return;
+
+	for (cpu = 0; cpu <= max_id; cpu++)
+		check_cpu_topology(cpu, poss_max_id);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 08/31] testcases: sysfs: Add sys_cpu_vulnerabilities01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (6 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 07/31] testcases: sysfs: Add sys_cpu_topology02 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 09/31] testcases: sysfs: Add sys_cpu_smt01 Cyril Hrubis
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/cpu/vulnerabilities/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../sysfs/devices/system/cpu/.gitignore       |  1 +
 .../system/cpu/sys_cpu_vulnerabilities01.c    | 85 +++++++++++++++++++
 3 files changed, 87 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_vulnerabilities01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index b1daab68f..663c1d203 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -4,3 +4,4 @@ sys_clocksource01 sys_clocksource01
 sys_node01 sys_node01
 sys_cpu_topology01 sys_cpu_topology01
 sys_cpu_topology02 sys_cpu_topology02
+sys_cpu_vulnerabilities01 sys_cpu_vulnerabilities01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/.gitignore b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
index a688a9b50..3ae5f494f 100644
--- a/testcases/kernel/sysfs/devices/system/cpu/.gitignore
+++ b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
@@ -1,2 +1,3 @@
 /sys_cpu_topology01
 /sys_cpu_topology02
+/sys_cpu_vulnerabilities01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_vulnerabilities01.c b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_vulnerabilities01.c
new file mode 100644
index 000000000..ae1bb8b7c
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_vulnerabilities01.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the CPU vulnerability reports exported under
+ * /sys/devices/system/cpu/vulnerabilities/.
+ *
+ * Each file describes the status of one hardware vulnerability. The kernel
+ * always prints a human readable status that starts with one of a few known
+ * prefixes. The test verifies that every vulnerability file:
+ *
+ * - is non-empty
+ * - starts with one of the known status prefixes (Not affected, Vulnerable,
+ *   Mitigation:, Unknown, Processor vulnerable)
+ *
+ * The test skips with TCONF when the vulnerabilities directory is not present,
+ * which is the case on architectures that do not report them.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define VULN "/sys/devices/system/cpu/vulnerabilities"
+
+static const char *const known_prefixes[] = {
+	"Not affected",
+	"Vulnerable",
+	"Mitigation:",
+	"Unknown",
+	"Processor vulnerable",
+};
+
+static void check_vuln(const char *name)
+{
+	char status[256] = "";
+	unsigned int i;
+
+	TST_SYSFS_READ_STR(status, sizeof(status), VULN "/%s", name);
+
+	if (status[0] == '\0') {
+		tst_res(TFAIL, "%s/%s is empty", VULN, name);
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(known_prefixes); i++) {
+		if (!strncmp(status, known_prefixes[i],
+			     strlen(known_prefixes[i]))) {
+			tst_res(TPASS, "%s: '%s'", name, status);
+			return;
+		}
+	}
+
+	tst_res(TFAIL, "%s has unexpected status '%s'", name, status);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+
+	if (access(VULN, F_OK)) {
+		tst_res(TCONF, VULN " is not available");
+		return;
+	}
+
+	d = SAFE_OPENDIR(VULN);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		check_vuln(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 09/31] testcases: sysfs: Add sys_cpu_smt01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (7 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 08/31] testcases: sysfs: Add sys_cpu_vulnerabilities01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 10/31] testcases: sysfs: Add sys_cpu_cache01 Cyril Hrubis
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/cpu/smt/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../sysfs/devices/system/cpu/.gitignore       |  1 +
 .../sysfs/devices/system/cpu/sys_cpu_smt01.c  | 65 +++++++++++++++++++
 3 files changed, 67 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_smt01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 663c1d203..4cc2360c6 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -5,3 +5,4 @@ sys_node01 sys_node01
 sys_cpu_topology01 sys_cpu_topology01
 sys_cpu_topology02 sys_cpu_topology02
 sys_cpu_vulnerabilities01 sys_cpu_vulnerabilities01
+sys_cpu_smt01 sys_cpu_smt01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/.gitignore b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
index 3ae5f494f..d0f8a6dab 100644
--- a/testcases/kernel/sysfs/devices/system/cpu/.gitignore
+++ b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
@@ -1,3 +1,4 @@
 /sys_cpu_topology01
 /sys_cpu_topology02
 /sys_cpu_vulnerabilities01
+/sys_cpu_smt01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_smt01.c b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_smt01.c
new file mode 100644
index 000000000..ae9612955
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_smt01.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the SMT (simultaneous multithreading) control exported
+ * under /sys/devices/system/cpu/smt/.
+ *
+ * The test verifies that:
+ *
+ * - active is a boolean (0 or 1)
+ * - control is one of the known states (on, off, forceoff, notsupported,
+ *   notimplemented)
+ * - when SMT is not usable (control is notsupported or notimplemented) active
+ *   reads back as 0
+ *
+ * The test skips with TCONF when the smt directory is not present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SMT "/sys/devices/system/cpu/smt"
+
+static const char *const control_allowed[] = {
+	"on", "off", "forceoff", "notsupported", "notimplemented", NULL
+};
+
+static void do_test(void)
+{
+	char control[32];
+	long active;
+
+	if (access(SMT, F_OK)) {
+		tst_res(TCONF, SMT " is not available");
+		return;
+	}
+
+	TST_SYSFS_ASSERT_BOOL(SMT "/active");
+
+	TST_SYSFS_ASSERT_ONEOF(control_allowed, SMT "/control");
+
+	TST_SYSFS_READ_STR(control, sizeof(control), SMT "/control");
+	active = TST_SYSFS_READ_LI(SMT "/active");
+
+	if (!strcmp(control, "notsupported") ||
+	    !strcmp(control, "notimplemented")) {
+		if (active == 0) {
+			tst_res(TPASS,
+				"active is 0 while SMT control is '%s'", control);
+		} else {
+			tst_res(TFAIL,
+				"active is %ld while SMT control is '%s'",
+				active, control);
+		}
+	}
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 10/31] testcases: sysfs: Add sys_cpu_cache01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (8 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 09/31] testcases: sysfs: Add sys_cpu_smt01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 11/31] testcases: sysfs: Add sys_clockevents01 Cyril Hrubis
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/cpu/cpu*/cache/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 .../sysfs/devices/system/cpu/.gitignore       |   1 +
 .../devices/system/cpu/sys_cpu_cache01.c      | 100 ++++++++++++++++++
 3 files changed, 102 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/cpu/sys_cpu_cache01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 4cc2360c6..928b85928 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -6,3 +6,4 @@ sys_cpu_topology01 sys_cpu_topology01
 sys_cpu_topology02 sys_cpu_topology02
 sys_cpu_vulnerabilities01 sys_cpu_vulnerabilities01
 sys_cpu_smt01 sys_cpu_smt01
+sys_cpu_cache01 sys_cpu_cache01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/.gitignore b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
index d0f8a6dab..8da0a61c0 100644
--- a/testcases/kernel/sysfs/devices/system/cpu/.gitignore
+++ b/testcases/kernel/sysfs/devices/system/cpu/.gitignore
@@ -2,3 +2,4 @@
 /sys_cpu_topology02
 /sys_cpu_vulnerabilities01
 /sys_cpu_smt01
+/sys_cpu_cache01
diff --git a/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_cache01.c b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_cache01.c
new file mode 100644
index 000000000..16ccf0eba
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/cpu/sys_cpu_cache01.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the CPU cache attributes exported under
+ * /sys/devices/system/cpu/cpuN/cache/indexM/.
+ *
+ * For every cache index of every online CPU the test verifies that:
+ *
+ * - level is greater than zero
+ * - type is one of Data, Instruction or Unified
+ * - coherency_line_size is a power of two
+ * - number_of_sets and ways_of_associativity are greater than zero
+ * - shared_cpu_list is a subset of the online CPUs
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_CPU "/sys/devices/system/cpu"
+
+static const char *const type_allowed[] = {
+	"Data", "Instruction", "Unified", NULL
+};
+
+static void check_index(int cpu, const char *index)
+{
+	char shared_cpu_list[PATH_MAX];
+
+	tst_res(TINFO, "Checking cpu%d %s", cpu, index);
+
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX,
+			       SYS_CPU "/cpu%d/cache/%s/level", cpu, index);
+
+	TST_SYSFS_ASSERT_ONEOF(type_allowed,
+			       SYS_CPU "/cpu%d/cache/%s/type", cpu, index);
+
+	TST_SYSFS_ASSERT_POW2(SYS_CPU "/cpu%d/cache/%s/coherency_line_size",
+			      cpu, index);
+
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX,
+			       SYS_CPU "/cpu%d/cache/%s/number_of_sets",
+			       cpu, index);
+
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX,
+			       SYS_CPU "/cpu%d/cache/%s/ways_of_associativity",
+			       cpu, index);
+
+	snprintf(shared_cpu_list, sizeof(shared_cpu_list),
+		 SYS_CPU "/cpu%d/cache/%s/shared_cpu_list", cpu, index);
+	TST_SYSFS_ASSERT_LIST_SUBSET(shared_cpu_list, SYS_CPU "/online");
+}
+
+static void check_cpu_caches(int cpu)
+{
+	char cache_dir[PATH_MAX];
+	DIR *d;
+	struct dirent *ent;
+
+	snprintf(cache_dir, sizeof(cache_dir), SYS_CPU "/cpu%d/cache", cpu);
+
+	if (access(cache_dir, F_OK))
+		return;
+
+	d = SAFE_OPENDIR(cache_dir);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "index", 5))
+			continue;
+
+		check_index(cpu, ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+}
+
+static void do_test(void)
+{
+	int count, max_id, cpu;
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_CPU "/online"))
+		return;
+
+	for (cpu = 0; cpu <= max_id; cpu++)
+		check_cpu_caches(cpu);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 11/31] testcases: sysfs: Add sys_clockevents01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (9 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 10/31] testcases: sysfs: Add sys_cpu_cache01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 12/31] testcases: sysfs: Add sys_ata01 Cyril Hrubis
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/devices/system/clockevents/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../devices/system/clockevents/.gitignore     |  1 +
 .../sysfs/devices/system/clockevents/Makefile |  7 ++
 .../system/clockevents/sys_clockevents01.c    | 88 +++++++++++++++++++
 4 files changed, 97 insertions(+)
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/.gitignore
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/Makefile
 create mode 100644 testcases/kernel/sysfs/devices/system/clockevents/sys_clockevents01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 928b85928..3015c03f5 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -7,3 +7,4 @@ sys_cpu_topology02 sys_cpu_topology02
 sys_cpu_vulnerabilities01 sys_cpu_vulnerabilities01
 sys_cpu_smt01 sys_cpu_smt01
 sys_cpu_cache01 sys_cpu_cache01
+sys_clockevents01 sys_clockevents01
diff --git a/testcases/kernel/sysfs/devices/system/clockevents/.gitignore b/testcases/kernel/sysfs/devices/system/clockevents/.gitignore
new file mode 100644
index 000000000..9379d10dd
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clockevents/.gitignore
@@ -0,0 +1 @@
+/sys_clockevents01
diff --git a/testcases/kernel/sysfs/devices/system/clockevents/Makefile b/testcases/kernel/sysfs/devices/system/clockevents/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clockevents/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/clockevents/sys_clockevents01.c b/testcases/kernel/sysfs/devices/system/clockevents/sys_clockevents01.c
new file mode 100644
index 000000000..3e1a46a50
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/clockevents/sys_clockevents01.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the per-CPU clock event devices exported under
+ * /sys/devices/system/clockevents/clockeventN/.
+ *
+ * The kernel registers one clock event device per online CPU (broadcast is a
+ * separate, additional entry used for CPUs whose local timer stops in idle).
+ * On platforms without a per-CPU timer, secondary CPUs may instead rely
+ * entirely on a broadcast IPI from a single shared timer without ever
+ * getting their own clockeventN entry, so the number of clockeventN
+ * directories is only asserted not to exceed the number of online CPUs
+ * rather than to match it exactly. The test verifies that:
+ *
+ * - there is at least one clockeventN directory
+ * - the number of clockeventN directories does not exceed the number of
+ *   online CPUs
+ * - each clockeventN/current_device names a non-empty clock event device
+ *
+ * The test skips with TCONF when no clockevents directory is present.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define CLOCKEVENTS "/sys/devices/system/clockevents"
+#define SYS_CPU "/sys/devices/system/cpu"
+
+static void check_clockevent(const char *name)
+{
+	char device[64] = "";
+
+	TST_SYSFS_READ_STR(device, sizeof(device),
+			   CLOCKEVENTS "/%s/current_device", name);
+
+	if (device[0] != '\0')
+		tst_res(TPASS, "%s/current_device = '%s'", name, device);
+	else
+		tst_res(TFAIL, "%s/current_device is empty", name);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int nclockevents = 0;
+	int online_count, max_id;
+
+	d = SAFE_OPENDIR(CLOCKEVENTS);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "clockevent", 10))
+			continue;
+
+		nclockevents++;
+		check_clockevent(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!nclockevents) {
+		tst_res(TCONF, "No clockevent directory found");
+		return;
+	}
+
+	if (TST_SYSFS_ASSERT_PARSE_LIST(&online_count, &max_id,
+					SYS_CPU "/online"))
+		return;
+
+	if (nclockevents <= online_count) {
+		tst_res(TPASS,
+			"number of clockevent devices (%d) does not exceed online CPUs (%d)",
+			nclockevents, online_count);
+	} else {
+		tst_res(TFAIL,
+			"number of clockevent devices (%d) exceeds online CPUs (%d)",
+			nclockevents, online_count);
+	}
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 12/31] testcases: sysfs: Add sys_ata01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (10 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 11/31] testcases: sysfs: Add sys_clockevents01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 13/31] testcases: sysfs: Add sys_bdi01 Cyril Hrubis
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/class/ata_device/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 testcases/kernel/sysfs/class/Makefile         |  7 ++
 .../kernel/sysfs/class/ata_device/.gitignore  |  1 +
 .../kernel/sysfs/class/ata_device/Makefile    |  7 ++
 .../kernel/sysfs/class/ata_device/sys_ata01.c | 93 +++++++++++++++++++
 5 files changed, 109 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/Makefile
 create mode 100644 testcases/kernel/sysfs/class/ata_device/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/ata_device/Makefile
 create mode 100644 testcases/kernel/sysfs/class/ata_device/sys_ata01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 3015c03f5..b5e644237 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -8,3 +8,4 @@ sys_cpu_vulnerabilities01 sys_cpu_vulnerabilities01
 sys_cpu_smt01 sys_cpu_smt01
 sys_cpu_cache01 sys_cpu_cache01
 sys_clockevents01 sys_clockevents01
+sys_ata01 sys_ata01
diff --git a/testcases/kernel/sysfs/class/Makefile b/testcases/kernel/sysfs/class/Makefile
new file mode 100644
index 000000000..5c8e7a249
--- /dev/null
+++ b/testcases/kernel/sysfs/class/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/class/ata_device/.gitignore b/testcases/kernel/sysfs/class/ata_device/.gitignore
new file mode 100644
index 000000000..b5b1f8f4a
--- /dev/null
+++ b/testcases/kernel/sysfs/class/ata_device/.gitignore
@@ -0,0 +1 @@
+/sys_ata01
diff --git a/testcases/kernel/sysfs/class/ata_device/Makefile b/testcases/kernel/sysfs/class/ata_device/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/ata_device/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/ata_device/sys_ata01.c b/testcases/kernel/sysfs/class/ata_device/sys_ata01.c
new file mode 100644
index 000000000..ab31c05e0
--- /dev/null
+++ b/testcases/kernel/sysfs/class/ata_device/sys_ata01.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the ATA device attributes exported under
+ * /sys/class/ata_device/<dev>/.
+ *
+ * For every ATA device the test verifies that:
+ *
+ * - class is one of the known libata device classes (ata, atapi, pmp, semb,
+ *   unknown)
+ * - dma_mode, pio_mode and xfer_mode, when non-empty, start with the "XFER_"
+ *   prefix used by all libata transfer mode names
+ * - spdn_cnt (speed-down count) is non-negative
+ *
+ * dma_mode is legitimately empty for devices that only support PIO, so it is
+ * only checked when present.
+ *
+ * The test skips with TCONF when no ATA device is present.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define ATA_DEVICE "/sys/class/ata_device"
+
+static const char *const class_allowed[] = {
+	"ata", "atapi", "pmp", "semb", "unknown", NULL
+};
+
+static void check_mode(const char *dev, const char *attr)
+{
+	char mode[32];
+
+	if (!TST_SYSFS_READ_STR(mode, sizeof(mode),
+				ATA_DEVICE "/%s/%s", dev, attr)) {
+		tst_res(TCONF, "%s/%s does not exist", dev, attr);
+		return;
+	}
+
+	if (mode[0] == '\0') {
+		tst_res(TCONF, "%s/%s is empty", dev, attr);
+		return;
+	}
+
+	if (!strncmp(mode, "XFER_", 5))
+		tst_res(TPASS, "%s/%s = '%s'", dev, attr, mode);
+	else
+		tst_res(TFAIL, "%s/%s = '%s' has no XFER_ prefix",
+			dev, attr, mode);
+}
+
+static void check_device(const char *dev)
+{
+	tst_res(TINFO, "Checking ata device '%s'", dev);
+
+	TST_SYSFS_ASSERT_ONEOF(class_allowed, ATA_DEVICE "/%s/class", dev);
+	check_mode(dev, "dma_mode");
+	check_mode(dev, "pio_mode");
+	check_mode(dev, "xfer_mode");
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, ATA_DEVICE "/%s/spdn_cnt", dev);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(ATA_DEVICE);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		found = 1;
+		check_device(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No ATA device found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 13/31] testcases: sysfs: Add sys_bdi01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (11 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 12/31] testcases: sysfs: Add sys_ata01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 14/31] testcases: sysfs: sys_hwmon01 Cyril Hrubis
                   ` (17 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/class/bdi/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |  1 +
 testcases/kernel/sysfs/class/bdi/.gitignore  |  1 +
 testcases/kernel/sysfs/class/bdi/Makefile    |  7 ++
 testcases/kernel/sysfs/class/bdi/sys_bdi01.c | 85 ++++++++++++++++++++
 4 files changed, 94 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/bdi/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/bdi/Makefile
 create mode 100644 testcases/kernel/sysfs/class/bdi/sys_bdi01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index b5e644237..c0983a5be 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -9,3 +9,4 @@ sys_cpu_smt01 sys_cpu_smt01
 sys_cpu_cache01 sys_cpu_cache01
 sys_clockevents01 sys_clockevents01
 sys_ata01 sys_ata01
+sys_bdi01 sys_bdi01
diff --git a/testcases/kernel/sysfs/class/bdi/.gitignore b/testcases/kernel/sysfs/class/bdi/.gitignore
new file mode 100644
index 000000000..93ce63f2c
--- /dev/null
+++ b/testcases/kernel/sysfs/class/bdi/.gitignore
@@ -0,0 +1 @@
+/sys_bdi01
diff --git a/testcases/kernel/sysfs/class/bdi/Makefile b/testcases/kernel/sysfs/class/bdi/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/bdi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/bdi/sys_bdi01.c b/testcases/kernel/sysfs/class/bdi/sys_bdi01.c
new file mode 100644
index 000000000..9b5df8877
--- /dev/null
+++ b/testcases/kernel/sysfs/class/bdi/sys_bdi01.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the backing device info (BDI) attributes exported under
+ * /sys/class/bdi/<id>/.
+ *
+ * For every BDI the test verifies that:
+ *
+ * - min_ratio <= max_ratio (both are percentages)
+ * - min_ratio_fine <= max_ratio_fine (both are in parts-per-million, i.e.
+ *   100% == 1000000)
+ * - min_bytes <= max_bytes, when max_bytes is set (0 means "no limit")
+ * - stable_pages_required and strict_limit are booleans
+ * - read_ahead_kb is non-negative
+ *
+ * The test skips with TCONF when no BDI is present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define BDI_CLASS "/sys/class/bdi"
+
+static void check_bdi(const char *id)
+{
+	long max_bytes;
+
+	tst_res(TINFO, "Checking bdi '%s'", id);
+
+	TST_SYSFS_ASSERT_RANGELL(0, 100, BDI_CLASS "/%s/min_ratio", id);
+	TST_SYSFS_ASSERT_RANGELL(0, 100, BDI_CLASS "/%s/max_ratio", id);
+	TST_SYSFS_ASSERT_LE_SUFFIX("min_ratio", "max_ratio",
+				   BDI_CLASS "/%s/", id);
+
+	TST_SYSFS_ASSERT_RANGELL(0, 1000000, BDI_CLASS "/%s/min_ratio_fine", id);
+	TST_SYSFS_ASSERT_RANGELL(0, 1000000, BDI_CLASS "/%s/max_ratio_fine", id);
+	TST_SYSFS_ASSERT_LE_SUFFIX("min_ratio_fine", "max_ratio_fine",
+				   BDI_CLASS "/%s/", id);
+
+	max_bytes = TST_SYSFS_READ_LI(BDI_CLASS "/%s/max_bytes", id);
+
+	if (max_bytes > 0) {
+		TST_SYSFS_ASSERT_LE_SUFFIX("min_bytes", "max_bytes",
+					   BDI_CLASS "/%s/", id);
+	}
+
+	TST_SYSFS_ASSERT_BOOL(BDI_CLASS "/%s/stable_pages_required", id);
+	TST_SYSFS_ASSERT_BOOL(BDI_CLASS "/%s/strict_limit", id);
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, BDI_CLASS "/%s/read_ahead_kb", id);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(BDI_CLASS);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		found = 1;
+		check_bdi(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No BDI found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 14/31] testcases: sysfs: sys_hwmon01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (12 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 13/31] testcases: sysfs: Add sys_bdi01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 15/31] testcases: sysfs: sys_leds01 Cyril Hrubis
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

Add test for /sys/class/hwmon/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/class/hwmon/.gitignore |   1 +
 testcases/kernel/sysfs/class/hwmon/Makefile   |   7 +
 .../kernel/sysfs/class/hwmon/sys_hwmon01.c    | 196 ++++++++++++++++++
 4 files changed, 205 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/hwmon/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/hwmon/Makefile
 create mode 100644 testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index c0983a5be..23c7adc7a 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -10,3 +10,4 @@ sys_cpu_cache01 sys_cpu_cache01
 sys_clockevents01 sys_clockevents01
 sys_ata01 sys_ata01
 sys_bdi01 sys_bdi01
+sys_hwmon01 sys_hwmon01
diff --git a/testcases/kernel/sysfs/class/hwmon/.gitignore b/testcases/kernel/sysfs/class/hwmon/.gitignore
new file mode 100644
index 000000000..f619181d7
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/.gitignore
@@ -0,0 +1 @@
+/sys_hwmon01
diff --git a/testcases/kernel/sysfs/class/hwmon/Makefile b/testcases/kernel/sysfs/class/hwmon/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c b/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
new file mode 100644
index 000000000..fb0077126
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Walks all hardware monitoring devices under /sys/class/hwmon/ and performs
+ * sanity and cross-checks on the sensor channels exported per the stable
+ * hwmon sysfs ABI (Documentation/hwmon/sysfs-interface.rst).
+ *
+ * For every discovered channel it verifies that:
+ *
+ * - tempN_input is within a plausible range (-55000 to 200000 millidegree
+ *   Celsius, i.e. within the extended-industrial silicon sensor rating floor
+ *   and below a generously high bound)
+ * - tempN_min <= tempN_max <= tempN_crit and tempN_lcrit <= tempN_min, when
+ *   the respective pair of attributes is present
+ * - tempN_alarm, tempN_crit_alarm, tempN_min_alarm, tempN_max_alarm and
+ *   tempN_fault are booleans, when present
+ * - fanN_input is within a plausible range (0 to 100000 RPM, comfortably
+ *   above even exotic small high-speed blower fans)
+ * - fanN_min <= fanN_max, when present
+ * - fanN_alarm, fanN_fault, fanN_min_alarm and fanN_max_alarm are booleans,
+ *   when present
+ * - inN_input is within a plausible range (-100000 to 100000 millivolt,
+ *   allowing for negative supply rails, not just positive ones)
+ * - inN_min <= inN_max <= inN_crit and inN_lcrit <= inN_min, when present
+ *   (inN channels are numbered from 0, unlike the other channel types which
+ *   start at 1)
+ * - inN_alarm and related alarm attributes are booleans, when present
+ * - currN_input is within a plausible range (-500000 to 500000 milliampere,
+ *   allowing for negative values on bidirectional/battery charge-discharge
+ *   current sensors)
+ * - currN_min <= currN_max, when present
+ * - powerN_input is within a plausible range (0 to 2000000000 microwatt,
+ *   i.e. 2 kW, chosen to stay within LONG_MAX on 32bit architectures as well)
+ * - pwmN is in the documented range [0, 255]
+ * - update_interval, when present, is within a plausible range (1 to
+ *   3600000 millisecond, i.e. up to an hour)
+ *
+ * All checks skip silently when a particular channel or attribute is not
+ * present, since the set of exposed sensors varies wildly between chips and
+ * platforms and up to MAX_CHANNELS indices are probed for every channel type
+ * regardless of how many are actually implemented by a given chip.
+ *
+ * Note: the ordering assumptions between minimum/maximum/critical thresholds
+ * reflect typical sensor configuration conventions, they are not enforced by
+ * the kernel itself, so in principle a chip could be configured in an
+ * unusual way that still is not actually a bug.
+ */
+
+#include <string.h>
+#include <dirent.h>
+
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define HWMON "/sys/class/hwmon"
+#define MAX_CHANNELS 32
+
+static void check_temp_channel(const char *hwmon, int idx)
+{
+	TST_SYSFS_ASSERT_RANGELL_SILENT(-55000, 200000,
+				      HWMON "/%s/temp%d_input", hwmon, idx);
+
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("lcrit", "min",
+					  HWMON "/%s/temp%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("min", "max",
+					  HWMON "/%s/temp%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("max", "crit",
+					  HWMON "/%s/temp%d_", hwmon, idx);
+
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/temp%d_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/temp%d_crit_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/temp%d_min_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/temp%d_max_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/temp%d_fault", hwmon, idx);
+}
+
+static void check_fan_channel(const char *hwmon, int idx)
+{
+	TST_SYSFS_ASSERT_RANGELL_SILENT(0, 100000,
+				      HWMON "/%s/fan%d_input", hwmon, idx);
+
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("min", "max",
+					  HWMON "/%s/fan%d_", hwmon, idx);
+
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/fan%d_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/fan%d_min_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/fan%d_max_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/fan%d_fault", hwmon, idx);
+}
+
+static void check_in_channel(const char *hwmon, int idx)
+{
+	TST_SYSFS_ASSERT_RANGELL_SILENT(-100000, 100000,
+				      HWMON "/%s/in%d_input", hwmon, idx);
+
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("lcrit", "min",
+					  HWMON "/%s/in%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("min", "max",
+					  HWMON "/%s/in%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("max", "crit",
+					  HWMON "/%s/in%d_", hwmon, idx);
+
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/in%d_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/in%d_min_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/in%d_max_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/in%d_crit_alarm", hwmon, idx);
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/in%d_lcrit_alarm", hwmon, idx);
+}
+
+static void check_curr_channel(const char *hwmon, int idx)
+{
+	/*
+	 * Unlike fan/power, current can be negative on bidirectional sensors,
+	 * e.g. a battery charge/discharge current monitor.
+	 */
+	TST_SYSFS_ASSERT_RANGELL_SILENT(-500000, 500000,
+				      HWMON "/%s/curr%d_input", hwmon, idx);
+
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("lcrit", "min",
+					  HWMON "/%s/curr%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("min", "max",
+					  HWMON "/%s/curr%d_", hwmon, idx);
+	TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("max", "crit",
+					  HWMON "/%s/curr%d_", hwmon, idx);
+
+	TST_SYSFS_ASSERT_BOOL_SILENT(HWMON "/%s/curr%d_alarm", hwmon, idx);
+}
+
+static void check_power_channel(const char *hwmon, int idx)
+{
+	/*
+	 * 2000000000 uW (2 kW) comfortably covers any single-component power
+	 * sensor (CPU, GPU, PSU rail, ...) while still fitting within
+	 * LONG_MAX on 32bit architectures.
+	 */
+	TST_SYSFS_ASSERT_RANGELL_SILENT(0, 2000000000,
+				      HWMON "/%s/power%d_input", hwmon, idx);
+}
+
+static void check_pwm_channel(const char *hwmon, int idx)
+{
+	TST_SYSFS_ASSERT_RANGELL_SILENT(0, 255, HWMON "/%s/pwm%d", hwmon, idx);
+}
+
+static void check_hwmon_device(const char *hwmon)
+{
+	char name[64];
+	int i;
+
+	if (TST_SYSFS_READ_STR(name, sizeof(name), HWMON "/%s/name", hwmon))
+		tst_res(TINFO, "%s: name = '%s'", hwmon, name);
+
+	TST_SYSFS_ASSERT_RANGELL_SILENT(1, 3600000,
+				      HWMON "/%s/update_interval", hwmon);
+
+	/* inN channels are numbered from 0, per the hwmon ABI */
+	for (i = 0; i <= MAX_CHANNELS; i++)
+		check_in_channel(hwmon, i);
+
+	for (i = 1; i <= MAX_CHANNELS; i++) {
+		check_temp_channel(hwmon, i);
+		check_fan_channel(hwmon, i);
+		check_curr_channel(hwmon, i);
+		check_power_channel(hwmon, i);
+		check_pwm_channel(hwmon, i);
+	}
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(HWMON);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "hwmon", 5))
+			continue;
+
+		found = 1;
+		check_hwmon_device(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No hwmon device found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 15/31] testcases: sysfs: sys_leds01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (13 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 14/31] testcases: sysfs: sys_hwmon01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 16/31] testcases: sysfs: Add sys_wakeup01 Cyril Hrubis
                   ` (15 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

Add test for /sys/class/leds/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 testcases/kernel/sysfs/class/leds/.gitignore  |  1 +
 testcases/kernel/sysfs/class/leds/Makefile    |  7 ++
 .../kernel/sysfs/class/leds/sys_leds01.c      | 67 +++++++++++++++++++
 4 files changed, 76 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/leds/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/leds/Makefile
 create mode 100644 testcases/kernel/sysfs/class/leds/sys_leds01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 23c7adc7a..f515c4c1e 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -11,3 +11,4 @@ sys_clockevents01 sys_clockevents01
 sys_ata01 sys_ata01
 sys_bdi01 sys_bdi01
 sys_hwmon01 sys_hwmon01
+sys_leds01 sys_leds01
diff --git a/testcases/kernel/sysfs/class/leds/.gitignore b/testcases/kernel/sysfs/class/leds/.gitignore
new file mode 100644
index 000000000..96e026b0d
--- /dev/null
+++ b/testcases/kernel/sysfs/class/leds/.gitignore
@@ -0,0 +1 @@
+/sys_leds01
diff --git a/testcases/kernel/sysfs/class/leds/Makefile b/testcases/kernel/sysfs/class/leds/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/leds/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/leds/sys_leds01.c b/testcases/kernel/sysfs/class/leds/sys_leds01.c
new file mode 100644
index 000000000..5b840f5e5
--- /dev/null
+++ b/testcases/kernel/sysfs/class/leds/sys_leds01.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the LED class devices exported under
+ * /sys/class/leds/<led>/.
+ *
+ * For every LED the test verifies that:
+ *
+ * - max_brightness is greater than zero
+ * - brightness is in the range [0, max_brightness]
+ * - trigger is a bracketed-choice file listing the available triggers with
+ *   exactly one of them selected via [] (the set of triggers is not checked
+ *   as it is highly kernel- and hardware-dependent)
+ *
+ * The test skips with TCONF when no LED device is present.
+ */
+
+#include <limits.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define LEDS "/sys/class/leds"
+
+static void check_led(const char *name)
+{
+	char trigger[64];
+
+	tst_res(TINFO, "Checking led '%s'", name);
+
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, LEDS "/%s/max_brightness", name);
+
+	TST_SYSFS_ASSERT_RANGELF(0, "brightness", "max_brightness",
+				  LEDS "/%s/", name);
+
+	TST_SYSFS_ASSERT_CHOICE(NULL, trigger, sizeof(trigger),
+				LEDS "/%s/trigger", name);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(LEDS);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		found = 1;
+		check_led(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No LED device found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 16/31] testcases: sysfs: Add sys_wakeup01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (14 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 15/31] testcases: sysfs: sys_leds01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 17/31] testcases: sysfs: Add sys_rtc01 Cyril Hrubis
                   ` (14 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/class/wakeup/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../kernel/sysfs/class/wakeup/.gitignore      |  1 +
 testcases/kernel/sysfs/class/wakeup/Makefile  |  7 ++
 .../kernel/sysfs/class/wakeup/sys_wakeup01.c  | 93 +++++++++++++++++++
 4 files changed, 102 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/wakeup/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/wakeup/Makefile
 create mode 100644 testcases/kernel/sysfs/class/wakeup/sys_wakeup01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index f515c4c1e..b8d003228 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -12,3 +12,4 @@ sys_ata01 sys_ata01
 sys_bdi01 sys_bdi01
 sys_hwmon01 sys_hwmon01
 sys_leds01 sys_leds01
+sys_wakeup01 sys_wakeup01
diff --git a/testcases/kernel/sysfs/class/wakeup/.gitignore b/testcases/kernel/sysfs/class/wakeup/.gitignore
new file mode 100644
index 000000000..16d0375cd
--- /dev/null
+++ b/testcases/kernel/sysfs/class/wakeup/.gitignore
@@ -0,0 +1 @@
+/sys_wakeup01
diff --git a/testcases/kernel/sysfs/class/wakeup/Makefile b/testcases/kernel/sysfs/class/wakeup/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/wakeup/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/wakeup/sys_wakeup01.c b/testcases/kernel/sysfs/class/wakeup/sys_wakeup01.c
new file mode 100644
index 000000000..cae83f7f7
--- /dev/null
+++ b/testcases/kernel/sysfs/class/wakeup/sys_wakeup01.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the wakeup source statistics exported under
+ * /sys/class/wakeup/wakeupN/.
+ *
+ * For every wakeup source the test verifies that:
+ *
+ * - name is a non-empty string
+ * - active_count, active_time_ms, event_count, expire_count, last_change_ms,
+ *   max_time_ms, prevent_suspend_time_ms, total_time_ms and wakeup_count are
+ *   all non-negative
+ * - active_time_ms <= total_time_ms (currently active time is part of the
+ *   accumulated total)
+ * - max_time_ms <= total_time_ms (the longest single event cannot exceed the
+ *   accumulated total)
+ *
+ * The test skips with TCONF when no wakeup source is present.
+ */
+
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define WAKEUP "/sys/class/wakeup"
+
+static const char *const nonneg_attrs[] = {
+	"active_count",
+	"active_time_ms",
+	"event_count",
+	"expire_count",
+	"last_change_ms",
+	"max_time_ms",
+	"prevent_suspend_time_ms",
+	"total_time_ms",
+	"wakeup_count",
+};
+
+static void check_wakeup(const char *dev)
+{
+	char name[128] = "";
+	unsigned int i;
+
+	tst_res(TINFO, "Checking %s", dev);
+
+	TST_SYSFS_READ_STR(name, sizeof(name), WAKEUP "/%s/name", dev);
+
+	if (name[0] != '\0')
+		tst_res(TPASS, "%s: name = '%s'", dev, name);
+	else
+		tst_res(TFAIL, "%s: name is empty", dev);
+
+	for (i = 0; i < ARRAY_SIZE(nonneg_attrs); i++) {
+		TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, WAKEUP "/%s/%s", dev,
+				       nonneg_attrs[i]);
+	}
+
+	TST_SYSFS_ASSERT_LE_SUFFIX("active_time_ms", "total_time_ms",
+				   WAKEUP "/%s/", dev);
+	TST_SYSFS_ASSERT_LE_SUFFIX("max_time_ms", "total_time_ms",
+				   WAKEUP "/%s/", dev);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(WAKEUP);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "wakeup", 6))
+			continue;
+
+		found = 1;
+		check_wakeup(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No wakeup source found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 17/31] testcases: sysfs: Add sys_rtc01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (15 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 16/31] testcases: sysfs: Add sys_wakeup01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 18/31] testcases: sysfs: Add sys_thermal01 Cyril Hrubis
                   ` (13 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/class/rtc/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |   1 +
 testcases/kernel/sysfs/class/rtc/.gitignore  |   1 +
 testcases/kernel/sysfs/class/rtc/Makefile    |   7 +
 testcases/kernel/sysfs/class/rtc/sys_rtc01.c | 200 +++++++++++++++++++
 4 files changed, 209 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/rtc/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/rtc/Makefile
 create mode 100644 testcases/kernel/sysfs/class/rtc/sys_rtc01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index b8d003228..de58499be 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -13,3 +13,4 @@ sys_bdi01 sys_bdi01
 sys_hwmon01 sys_hwmon01
 sys_leds01 sys_leds01
 sys_wakeup01 sys_wakeup01
+sys_rtc01 sys_rtc01
diff --git a/testcases/kernel/sysfs/class/rtc/.gitignore b/testcases/kernel/sysfs/class/rtc/.gitignore
new file mode 100644
index 000000000..0ce416b43
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/.gitignore
@@ -0,0 +1 @@
+/sys_rtc01
diff --git a/testcases/kernel/sysfs/class/rtc/Makefile b/testcases/kernel/sysfs/class/rtc/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/rtc/sys_rtc01.c b/testcases/kernel/sysfs/class/rtc/sys_rtc01.c
new file mode 100644
index 000000000..6ae1b4465
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/sys_rtc01.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the RTC attributes exported under
+ * /sys/class/rtc/rtcN/.
+ *
+ * date and time report the RTC clock in UTC, while since_epoch reports the
+ * same value pre-converted to a Unix timestamp. For every RTC device the
+ * test verifies that:
+ *
+ * - hctosys is a boolean
+ * - max_user_freq is greater than zero
+ * - since_epoch matches date and time converted to a Unix timestamp
+ * - for the RTC device with hctosys == 1, i.e. the one that was actually
+ *   used by the kernel's own CONFIG_RTC_HCTOSYS code to set the system clock
+ *   at boot, since_epoch is additionally close to the current system time(2)
+ *   value. On a system with multiple RTC devices the other ones are not
+ *   guaranteed to be in sync with the system clock at all (e.g. a
+ *   peripheral's battery-backed RTC used only for its own wakeup alarm), so
+ *   this second check is skipped for them.
+ *
+ * The hctosys only reflects whether the kernel itself performed the sync; many
+ * setups runs "hwclock --hctosys" from a userspace init script instead sync
+ * the system clock from the RTC without the kernel ever knowing about it,
+ * leaving hctosys == 0 on every device despite the clock legitimately being in
+ * sync. To still get coverage on such systems, if no RTC has hctosys == 1 at
+ * all, rtc0 is treated as the presumed primary RTC and gets the system time
+ * check anyway.
+ *
+ * The date/time vs since_epoch cross-check allows a few seconds of slack
+ * since they are read from separate files and the wall clock keeps ticking
+ * in between reads.
+ *
+ * The since_epoch vs system time(2) cross-check allows extra slack that
+ * grows with system uptime: the RTC is a free-running hardware crystal
+ * oscillator that (absent some periodic resync mechanism, which is not
+ * guaranteed to be running) drifts independently of the system clock the
+ * whole time the system is up, not just across the couple of reads this
+ * test performs, so on a long-running system a purely fixed slack would
+ * eventually produce false failures even on perfectly healthy hardware.
+ *
+ * The check may still fail if the system was booted with RTC that is fairly
+ * off the current time and the system was later synced with NTP.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/sysinfo.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define RTC_CLASS "/sys/class/rtc"
+#define SLACK_SEC 10
+
+/*
+ * Generously high assumed worst-case RTC crystal drift, in parts per
+ * million, used to scale the system time slack with uptime. 200 ppm covers
+ * even fairly poor/uncompensated RTC crystals across a wide temperature
+ * range; a decent RTC typically drifts far less than this.
+ */
+#define MAX_RTC_DRIFT_PPM 200
+
+static long sys_time_slack_sec(void)
+{
+	struct sysinfo info;
+
+	SAFE_SYSINFO(&info);
+
+	return SLACK_SEC + (long)info.uptime * MAX_RTC_DRIFT_PPM / 1000000;
+}
+
+static long read_hctosys(const char *name)
+{
+	return TST_SYSFS_READ_LI(RTC_CLASS "/%s/hctosys", name);
+}
+
+static void check_rtc(const char *name, int any_hctosys)
+{
+	char date[32] = "", rtc_time[32] = "";
+	struct tm tm = { 0 };
+	time_t rtc_epoch, sys_epoch;
+	long  hctosys, slack;
+	time_t since_epoch;
+	int is_primary;
+
+	TST_SYSFS_ASSERT_BOOL(RTC_CLASS "/%s/hctosys", name);
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, RTC_CLASS "/%s/max_user_freq", name);
+
+	hctosys = read_hctosys(name);
+
+	/*
+	 * If no RTC has hctosys == 1 anywhere (see the top comment), fall
+	 * back to treating rtc0 as the presumed primary RTC.
+	 */
+	is_primary = hctosys || (!any_hctosys && !strcmp(name, "rtc0"));
+
+	TST_SYSFS_READ_STR(date, sizeof(date), RTC_CLASS "/%s/date", name);
+	TST_SYSFS_READ_STR(rtc_time, sizeof(rtc_time), RTC_CLASS "/%s/time",
+			   name);
+
+	since_epoch = TST_SYSFS_READ_LI(RTC_CLASS "/%s/since_epoch", name);
+
+	sys_epoch = time(NULL);
+
+	if (sscanf(date, "%d-%d-%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3 ||
+	    sscanf(rtc_time, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
+		tst_res(TFAIL, "%s: failed to parse date '%s' / time '%s'",
+			name, date, rtc_time);
+		return;
+	}
+
+	tm.tm_year -= 1900;
+	tm.tm_mon -= 1;
+	rtc_epoch = timegm(&tm);
+
+	if (llabs((long long)(rtc_epoch - since_epoch)) <= SLACK_SEC) {
+		tst_res(TPASS,
+			"%s: since_epoch (%lld) matches date '%s' time '%s' (%lld)",
+			name, (long long)since_epoch, date, rtc_time,
+			(long long)rtc_epoch);
+	} else {
+		tst_res(TFAIL,
+			"%s: since_epoch (%lld) does not match date '%s' time '%s' (%lld)",
+			name, (long long)since_epoch, date, rtc_time,
+			(long long)rtc_epoch);
+	}
+
+	if (!is_primary) {
+		tst_res(TINFO,
+			"%s: not the (assumed) primary RTC, not checking since_epoch against system time",
+			name);
+		return;
+	}
+
+	slack = sys_time_slack_sec();
+	long long diff = llabs((long long)(since_epoch - sys_epoch));
+
+	if (diff <= slack) {
+		tst_res(TPASS,
+			"%s: since_epoch (%lld), system time (%lld), diff %llds <= slack %lds",
+			name, (long long)since_epoch, (long long)sys_epoch,
+			diff, slack);
+	} else {
+		tst_res(TFAIL,
+			"%s: since_epoch (%lld), system time (%lld), diff %llds > slack %lds",
+			name, (long long)since_epoch, (long long)sys_epoch,
+			diff, slack);
+	}
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+	int any_hctosys = 0;
+
+	d = SAFE_OPENDIR(RTC_CLASS);
+
+	/* First pass: find out whether any RTC has hctosys == 1 already. */
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "rtc", 3))
+			continue;
+
+		found = 1;
+
+		if (read_hctosys(ent->d_name))
+			any_hctosys = 1;
+	}
+
+	if (!found) {
+		tst_res(TCONF, "No RTC device found");
+		SAFE_CLOSEDIR(d);
+		return;
+	}
+
+	rewinddir(d);
+
+	/* Second pass: run the actual checks. */
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "rtc", 3))
+			continue;
+
+		tst_res(TINFO, "Checking %s", ent->d_name);
+		check_rtc(ent->d_name, any_hctosys);
+	}
+
+	SAFE_CLOSEDIR(d);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 18/31] testcases: sysfs: Add sys_thermal01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (16 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 17/31] testcases: sysfs: Add sys_rtc01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:12 ` [LTP] [PATCH v1 19/31] tst_netdevice: Add two more helper macros Cyril Hrubis
                   ` (12 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

A test for /sys/class/thermal/cooling_device*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../kernel/sysfs/class/thermal/.gitignore     |  1 +
 testcases/kernel/sysfs/class/thermal/Makefile |  7 ++
 .../sysfs/class/thermal/sys_thermal01.c       | 70 +++++++++++++++++++
 4 files changed, 79 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/thermal/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/thermal/Makefile
 create mode 100644 testcases/kernel/sysfs/class/thermal/sys_thermal01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index de58499be..454042ed0 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -14,3 +14,4 @@ sys_hwmon01 sys_hwmon01
 sys_leds01 sys_leds01
 sys_wakeup01 sys_wakeup01
 sys_rtc01 sys_rtc01
+sys_thermal01 sys_thermal01
diff --git a/testcases/kernel/sysfs/class/thermal/.gitignore b/testcases/kernel/sysfs/class/thermal/.gitignore
new file mode 100644
index 000000000..c551a2cde
--- /dev/null
+++ b/testcases/kernel/sysfs/class/thermal/.gitignore
@@ -0,0 +1 @@
+/sys_thermal01
diff --git a/testcases/kernel/sysfs/class/thermal/Makefile b/testcases/kernel/sysfs/class/thermal/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/thermal/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/thermal/sys_thermal01.c b/testcases/kernel/sysfs/class/thermal/sys_thermal01.c
new file mode 100644
index 000000000..8b2e5abff
--- /dev/null
+++ b/testcases/kernel/sysfs/class/thermal/sys_thermal01.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the thermal cooling devices exported under
+ * /sys/class/thermal/cooling_deviceN/.
+ *
+ * For every cooling device the test verifies that:
+ *
+ * - type is a non-empty string
+ * - max_state is non-negative
+ * - cur_state is in the range [0, max_state]
+ *
+ * The test skips with TCONF when no cooling device is present.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define THERMAL "/sys/class/thermal"
+
+static void check_cooling_device(const char *name)
+{
+	char type[64] = "";
+
+	tst_res(TINFO, "Checking %s", name);
+
+	TST_SYSFS_READ_STR(type, sizeof(type), THERMAL "/%s/type", name);
+
+	if (type[0] != '\0')
+		tst_res(TPASS, "%s: type = '%s'", name, type);
+	else
+		tst_res(TFAIL, "%s: type is empty", name);
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, THERMAL "/%s/max_state", name);
+
+	TST_SYSFS_ASSERT_RANGELF(0, "cur_state", "max_state",
+				  THERMAL "/%s/", name);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(THERMAL);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "cooling_device", 14))
+			continue;
+
+		found = 1;
+		check_cooling_device(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No cooling device found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 19/31] tst_netdevice: Add two more helper macros
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (17 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 18/31] testcases: sysfs: Add sys_thermal01 Cyril Hrubis
@ 2026-08-18 14:12 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 20/31] testcases: sysfs: Add sys_net01 Cyril Hrubis
                   ` (11 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:12 UTC (permalink / raw)
  To: ltp

- NETDEV_SET_HWADDR() helper to change MAC address
- NETDEV_SET_MASTER() helper to connect two devices as master-slave

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 include/tst_netdevice.h | 20 +++++++++
 lib/tst_netdevice.c     | 90 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 110 insertions(+)

diff --git a/include/tst_netdevice.h b/include/tst_netdevice.h
index 2617bb149..934ee5fb4 100644
--- a/include/tst_netdevice.h
+++ b/include/tst_netdevice.h
@@ -61,6 +61,26 @@ int tst_netdev_remove_address_inet(const char *file, const int lineno,
 #define NETDEV_REMOVE_ADDRESS_INET(ifname, address) \
 	tst_netdev_remove_address_inet(__FILE__, __LINE__, 1, (ifname), \
 		(address))
+/*
+ * Change the link-layer (MAC) address of an existing network device. Most
+ * drivers require the device to be administratively down for this to
+ * succeed.
+ */
+int tst_netdev_set_hwaddr(const char *file, const int lineno, int strict,
+	const char *ifname, const void *addr, size_t addrlen);
+#define NETDEV_SET_HWADDR(ifname, addr, addrlen) \
+	tst_netdev_set_hwaddr(__FILE__, __LINE__, 1, (ifname), (addr), \
+		(addrlen))
+
+/*
+ * Enslave ifname to the master_ifname device, e.g. add it as a bridge or bond
+ * port. Pass NULL as master_ifname to release the device from its current
+ * master.
+ */
+int tst_netdev_set_master(const char *file, const int lineno, int strict,
+	const char *ifname, const char *master_ifname);
+#define NETDEV_SET_MASTER(ifname, master_ifname) \
+	tst_netdev_set_master(__FILE__, __LINE__, 1, (ifname), (master_ifname))
 
 int tst_netdev_change_ns_fd(const char *file, const int lineno, int strict,
 	const char *ifname, int nsfd);
diff --git a/lib/tst_netdevice.c b/lib/tst_netdevice.c
index 1042466bf..0861d39fc 100644
--- a/lib/tst_netdevice.c
+++ b/lib/tst_netdevice.c
@@ -287,6 +287,96 @@ static int modify_address(const char *file, const int lineno, int strict,
 	return ret;
 }
 
+int tst_netdev_set_hwaddr(const char *file, const int lineno, int strict,
+	const char *ifname, const void *addr, size_t addrlen)
+{
+	struct ifinfomsg info = { .ifi_family = AF_UNSPEC };
+	struct tst_netlink_context *ctx;
+	int ret;
+
+	if (strlen(ifname) >= IFNAMSIZ) {
+		tst_brk_(file, lineno, TBROK,
+			"Network device name \"%s\" too long", ifname);
+		return 0;
+	}
+
+	ctx = create_request(file, lineno, RTM_NEWLINK, 0, &info, sizeof(info));
+
+	if (!ctx)
+		return 0;
+
+	if (!tst_rtnl_add_attr_string(file, lineno, ctx, IFLA_IFNAME, ifname)) {
+		tst_netlink_destroy_context(file, lineno, ctx);
+		return 0;
+	}
+
+	if (!tst_rtnl_add_attr(file, lineno, ctx, IFLA_ADDRESS, addr, addrlen)) {
+		tst_netlink_destroy_context(file, lineno, ctx);
+		return 0;
+	}
+
+	ret = tst_netlink_send_validate(file, lineno, ctx);
+	tst_netlink_destroy_context(file, lineno, ctx);
+
+	if (strict && !ret) {
+		tst_brk_(file, lineno, TBROK,
+			"Failed to set hwaddr for %s: %s", ifname,
+			tst_strerrno(tst_netlink_errno));
+	}
+
+	return ret;
+}
+
+int tst_netdev_set_master(const char *file, const int lineno, int strict,
+	const char *ifname, const char *master_ifname)
+{
+	struct ifinfomsg info = { .ifi_family = AF_UNSPEC };
+	struct tst_netlink_context *ctx;
+	int32_t master_index = 0;
+	int ret;
+
+	if (strlen(ifname) >= IFNAMSIZ) {
+		tst_brk_(file, lineno, TBROK,
+			"Network device name \"%s\" too long", ifname);
+		return 0;
+	}
+
+	if (master_ifname) {
+		master_index = tst_netdev_index_by_name(file, lineno,
+			master_ifname);
+
+		if (master_index < 0)
+			return 0;
+	}
+
+	ctx = create_request(file, lineno, RTM_NEWLINK, 0, &info, sizeof(info));
+
+	if (!ctx)
+		return 0;
+
+	if (!tst_rtnl_add_attr_string(file, lineno, ctx, IFLA_IFNAME, ifname)) {
+		tst_netlink_destroy_context(file, lineno, ctx);
+		return 0;
+	}
+
+	if (!tst_rtnl_add_attr(file, lineno, ctx, IFLA_MASTER, &master_index,
+		sizeof(master_index))) {
+		tst_netlink_destroy_context(file, lineno, ctx);
+		return 0;
+	}
+
+	ret = tst_netlink_send_validate(file, lineno, ctx);
+	tst_netlink_destroy_context(file, lineno, ctx);
+
+	if (strict && !ret) {
+		tst_brk_(file, lineno, TBROK,
+			"Failed to set master for %s: %s", ifname,
+			tst_strerrno(tst_netlink_errno));
+	}
+
+	return ret;
+}
+
 int tst_netdev_add_address(const char *file, const int lineno, int strict,
 	const char *ifname, unsigned int family, const void *address,
 	unsigned int prefix, size_t addrlen, unsigned int flags)
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 20/31] testcases: sysfs: Add sys_net01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (18 preceding siblings ...)
  2026-08-18 14:12 ` [LTP] [PATCH v1 19/31] tst_netdevice: Add two more helper macros Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 21/31] testcases: sysfs: Add sys_net02 Cyril Hrubis
                   ` (10 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/class/net/*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |   1 +
 testcases/kernel/sysfs/class/net/.gitignore  |   1 +
 testcases/kernel/sysfs/class/net/Makefile    |   7 ++
 testcases/kernel/sysfs/class/net/sys_net01.c | 123 +++++++++++++++++++
 4 files changed, 132 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/net/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/net/Makefile
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 454042ed0..90e4d2b8e 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -15,3 +15,4 @@ sys_leds01 sys_leds01
 sys_wakeup01 sys_wakeup01
 sys_rtc01 sys_rtc01
 sys_thermal01 sys_thermal01
+sys_net01 sys_net01
diff --git a/testcases/kernel/sysfs/class/net/.gitignore b/testcases/kernel/sysfs/class/net/.gitignore
new file mode 100644
index 000000000..f5ef57b75
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/.gitignore
@@ -0,0 +1 @@
+/sys_net01
diff --git a/testcases/kernel/sysfs/class/net/Makefile b/testcases/kernel/sysfs/class/net/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/net/sys_net01.c b/testcases/kernel/sysfs/class/net/sys_net01.c
new file mode 100644
index 000000000..66c1d353f
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net01.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the network interface attributes exported under
+ * /sys/class/net/<iface>/.
+ *
+ * For every network interface the test verifies that:
+ *
+ * - mtu is greater than zero, and for an Ethernet-type interface (type == 1,
+ *   ARPHRD_ETHER, which also covers most virtual/Wi-Fi interfaces on Linux)
+ *   at most 65535, the largest possible IPv4 packet size and comfortably
+ *   above even the largest real-world jumbo frame configurations. Other
+ *   interface types (tunnels, PPP, CAN, ...) are left effectively unbounded
+ *   since their legitimate MTU semantics vary too much to bound generically,
+ *   e.g. a CAN interface reports the size of struct can_frame/canfd_frame
+ *   (16/72) rather than an IP MTU at all.
+ * - addr_len is at most MAX_ADDR_LEN (32), the fixed size of the kernel's
+ *   internal hardware address buffer (include/linux/netdevice.h) and thus a
+ *   hard limit rather than a mere plausibility bound. It is not asserted to
+ *   be non-zero: plenty of legitimate interfaces (PPP, GRE/IPIP/sit tunnels,
+ *   WireGuard, CAN, ...) have no link-layer address at all and report
+ *   addr_len == 0.
+ * - the address has exactly addr_len bytes (colon separated hex octets),
+ *   or is empty when addr_len == 0
+ * - operstate is one of the known states
+ * - carrier is a boolean (0 or 1) when readable
+ *
+ * carrier returns an error (EINVAL) when the interface is administratively
+ * down, which the test tolerates. The test skips with TCONF when no interface
+ * is present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define NET "/sys/class/net"
+
+/* include/linux/netdevice.h: fixed size of dev->dev_addr[]. */
+#define MAX_ADDR_LEN 32
+
+/* include/uapi/linux/if_arp.h */
+#define ARPHRD_ETHER 1
+
+/*
+ * The largest possible IPv4 packet size, used as a practical upper bound for
+ * the MTU of an Ethernet-type interface, see the top comment.
+ */
+#define MAX_ETHER_MTU 65535
+
+static const char *const operstates[] = {
+	"unknown", "notpresent", "down", "lowerlayerdown",
+	"testing", "dormant", "up", NULL
+};
+
+static void check_address(const char *iface)
+{
+	char addr[128] = "";
+	int octets;
+	char *p;
+
+	TST_SYSFS_READ_STR(addr, sizeof(addr), NET "/%s/address", iface);
+
+	/* An empty address is valid for an interface with addr_len == 0. */
+	octets = addr[0] ? 1 : 0;
+
+	for (p = addr; *p; p++) {
+		if (*p == ':')
+			octets++;
+	}
+
+	TST_SYSFS_EXP_EQ_LI(octets, NET "/%s/addr_len", iface);
+}
+
+static void check_iface(const char *iface)
+{
+	long type, mtu_max = LONG_MAX;
+
+	tst_res(TINFO, "Checking interface '%s'", iface);
+
+	type = TST_SYSFS_READ_LI(NET "/%s/type", iface);
+
+	if (type == ARPHRD_ETHER)
+		mtu_max = MAX_ETHER_MTU;
+
+	TST_SYSFS_ASSERT_RANGELL(1, mtu_max, NET "/%s/mtu", iface);
+	TST_SYSFS_ASSERT_RANGELL(0, MAX_ADDR_LEN, NET "/%s/addr_len", iface);
+
+	check_address(iface);
+	TST_SYSFS_ASSERT_ONEOF(operstates, NET "/%s/operstate", iface);
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(NET);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		found = 1;
+		check_iface(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No network interfaces found");
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 21/31] testcases: sysfs: Add sys_net02
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (19 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 20/31] testcases: sysfs: Add sys_net01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 22/31] testcases: sysfs: Add sys_net03 Cyril Hrubis
                   ` (9 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/class/net/*/* files.

This test, unlike sys_net01, creates a virtual ethernet interface so
that it can change various attributes and verify that they are mirrored
into the sysfs correctly.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/class/net/.gitignore   |   1 +
 testcases/kernel/sysfs/class/net/sys_net02.c  | 179 ++++++++++++++++++
 .../kernel/sysfs/class/net/sys_net_common.h   |  80 ++++++++
 4 files changed, 261 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net02.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net_common.h

diff --git a/runtest/sysfs b/runtest/sysfs
index 90e4d2b8e..fee8a0b2b 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -16,3 +16,4 @@ sys_wakeup01 sys_wakeup01
 sys_rtc01 sys_rtc01
 sys_thermal01 sys_thermal01
 sys_net01 sys_net01
+sys_net02 sys_net02
diff --git a/testcases/kernel/sysfs/class/net/.gitignore b/testcases/kernel/sysfs/class/net/.gitignore
index f5ef57b75..b70d02158 100644
--- a/testcases/kernel/sysfs/class/net/.gitignore
+++ b/testcases/kernel/sysfs/class/net/.gitignore
@@ -1 +1,2 @@
 /sys_net01
+/sys_net02
diff --git a/testcases/kernel/sysfs/class/net/sys_net02.c b/testcases/kernel/sysfs/class/net/sys_net02.c
new file mode 100644
index 000000000..f8f564c1f
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net02.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Verifies that the link state attributes exported under
+ * /sys/class/net/<iface>/ correctly mirror the administrative state of a
+ * veth peer.
+ *
+ * A veth device's carrier is expected to be up only while its peer is up:
+ * veth_open()/veth_close() call netif_carrier_on()/netif_carrier_off() based
+ * on whether the peer is running. The test creates a veth pair and checks
+ * that bringing the peer down and back up is reflected in:
+ *
+ * - carrier - 1 while the peer is up, 0 while it is down
+ * - operstate - "up" while the peer is up, "lowerlayerdown" while it is down
+ * - carrier_up_count / carrier_down_count - incremented by exactly one on
+ *   each corresponding transition
+ * - carrier_changes - always equal to carrier_up_count + carrier_down_count
+ *
+ * Note this only checks the values synchronously after each netlink state
+ * change (which is itself synchronous), it does not use poll(2)/select(2):
+ * unlike some other sysfs attributes, /sys/class/net/<iface>/carrier does not
+ * support poll() based notification on Linux, link state changes are
+ * reported to userspace via rtnetlink instead.
+ *
+ * The test also checks attributes that are not tied to the peer's state:
+ *
+ * - mtu - directly writable via sysfs; a few valid values are written and
+ *   read back, and one syntactically invalid value is rejected (with the mtu
+ *   left unchanged)
+ * - address - read-only in sysfs, changed via rtnetlink instead (the
+ *   interface is brought down first since most drivers require this); while
+ *   doing so, the IFF_UP bit in "flags" is also cross-checked against the
+ *   interface's own administrative state
+ * - netdev_group and ifalias - directly writable via sysfs, a value is
+ *   written and read back
+ *
+ * This needs root to create the veth pair.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <net/if.h>
+
+#include "tst_test.h"
+#include "tst_netdevice.h"
+#include "tst_sysfs_assert.h"
+
+#define IFNAME1 "ltp_vethp1"
+#define IFNAME2 "ltp_vethp2"
+#define IFACE_PATH "/sys/class/net/" IFNAME1
+
+static int veth_created;
+
+static long read_mtu(void)
+{
+	return TST_SYSFS_READ_LI(IFACE_PATH "/mtu");
+}
+
+#include "sys_net_common.h"
+
+static void setup(void)
+{
+	CREATE_VETH_PAIR(IFNAME1, IFNAME2);
+	veth_created = 1;
+
+	NETDEV_SET_STATE(IFNAME1, 1);
+	NETDEV_SET_STATE(IFNAME2, 1);
+}
+
+static unsigned long read_flags(void)
+{
+	return TST_SYSFS_READ_LX(IFACE_PATH "/flags");
+}
+
+static void check_address(void)
+{
+	static const unsigned char new_addr[6] = {0x02, 0, 0, 0, 0, 0x01};
+	char actual[32];
+	long iff_up;
+
+	tst_res(TINFO, "IFNAME1 is up before the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, IFF_UP);
+
+	NETDEV_SET_STATE(IFNAME1, 0);
+	tst_res(TINFO, "IFNAME1 is down for the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, 0);
+
+	NETDEV_SET_HWADDR(IFNAME1, new_addr, sizeof(new_addr));
+	NETDEV_SET_STATE(IFNAME1, 1);
+	tst_res(TINFO, "IFNAME1 is up again after the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, IFF_UP);
+
+	TST_SYSFS_READ_STR(actual, sizeof(actual), IFACE_PATH "/address");
+
+	TST_EXP_EQ_STR(actual, "02:00:00:00:00:01");
+}
+
+static void check_netdev_group(void)
+{
+	long group;
+
+	if (FILE_PRINTF(IFACE_PATH "/netdev_group", "%d", 7)) {
+		tst_res(TFAIL, "Failed to set netdev_group");
+		return;
+	}
+
+	group = TST_SYSFS_READ_LI(IFACE_PATH "/netdev_group");
+
+	TST_EXP_EQ_LI(group, 7);
+}
+
+static void check_ifalias(void)
+{
+	char alias[64];
+
+	if (FILE_PRINTF(IFACE_PATH "/ifalias", "%s", "ltp-test-alias")) {
+		tst_res(TFAIL, "Failed to set ifalias");
+		return;
+	}
+
+	TST_SYSFS_READ_STR(alias, sizeof(alias), IFACE_PATH "/ifalias");
+	TST_EXP_EQ_STR(alias, "ltp-test-alias");
+}
+
+static void run(void)
+{
+	struct netdev_state s0, s1, s2;
+
+	read_state(&s0);
+	check_state(&s0, 1, "up", "both ends up");
+
+	NETDEV_SET_STATE(IFNAME2, 0);
+	read_state(&s1);
+	check_state(&s1, 0, "lowerlayerdown", "peer down");
+	check_state_delta(&s0, &s1, 0, 1, "peer down transition");
+
+	NETDEV_SET_STATE(IFNAME2, 1);
+	read_state(&s2);
+	check_state(&s2, 1, "up", "peer back up");
+	check_state_delta(&s1, &s2, 1, 0, "peer up transition");
+
+	check_mtu_valid(68);
+	check_mtu_valid(1500);
+	check_mtu_valid(9000);
+	check_mtu_valid(65535);
+	check_mtu_invalid("-1");
+	check_mtu_invalid("0");
+	check_mtu_invalid("67");
+	check_mtu_invalid("70000");
+
+	check_address();
+
+	check_netdev_group();
+	check_ifalias();
+}
+
+static void cleanup(void)
+{
+	if (veth_created)
+		NETDEV_REMOVE_DEVICE(IFNAME1);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_VETH",
+		NULL
+	},
+};
diff --git a/testcases/kernel/sysfs/class/net/sys_net_common.h b/testcases/kernel/sysfs/class/net/sys_net_common.h
new file mode 100644
index 000000000..7b0346c9c
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net_common.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+#ifndef SYS_NET_COMMON
+#define SYS_NET_COMMON
+
+struct netdev_state {
+	long carrier;
+	long carrier_changes;
+	long carrier_up_count;
+	long carrier_down_count;
+	char operstate[32];
+};
+
+static void read_state(struct netdev_state *st)
+{
+	st->carrier = TST_SYSFS_READ_LI(IFACE_PATH "/carrier");
+	st->carrier_changes = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_changes");
+	st->carrier_up_count = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_up_count");
+	st->carrier_down_count = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_down_count");
+	TST_SYSFS_READ_STR(st->operstate, sizeof(st->operstate), IFACE_PATH "/operstate");
+}
+
+static void check_state(const struct netdev_state *st, long carrier,
+			const char *operstate, const char *desc)
+{
+	tst_res(TINFO, "%s", desc);
+
+	TST_EXP_EQ_LI(st->carrier, carrier);
+	TST_EXP_EQ_STR(st->operstate, operstate);
+	TST_EXP_EQ_LI(st->carrier_changes,
+		      st->carrier_up_count + st->carrier_down_count);
+}
+
+static void check_state_delta(const struct netdev_state *prev,
+			      const struct netdev_state *cur, long up_delta,
+			      long down_delta, const char *desc)
+{
+	long got_up = cur->carrier_up_count - prev->carrier_up_count;
+	long got_down = cur->carrier_down_count - prev->carrier_down_count;
+	long got_changes = cur->carrier_changes - prev->carrier_changes;
+
+	tst_res(TINFO, "%s", desc);
+
+	TST_EXP_EQ_LI(got_up, up_delta);
+	TST_EXP_EQ_LI(got_down, down_delta);
+	TST_EXP_EQ_LI(got_changes, up_delta + down_delta);
+}
+
+static void check_mtu_valid(long mtu)
+{
+	char mtu_str[16];
+
+	snprintf(mtu_str, sizeof(mtu_str), "%ld", mtu);
+
+	if (FILE_PRINTF(IFACE_PATH "/mtu", "%s", mtu_str)) {
+		tst_res(TFAIL, "Failed to set mtu to %ld", mtu);
+		return;
+	}
+
+	TST_EXP_EQ_LI(read_mtu(), mtu);
+}
+
+static void check_mtu_invalid(const char *mtu_str)
+{
+	long before = read_mtu();
+
+	if (!FILE_PRINTF(IFACE_PATH "/mtu", "%s", mtu_str)) {
+		tst_res(TFAIL, "Writing mtu '%s' unexpectedly succeeded",
+			mtu_str);
+		return;
+	}
+
+	tst_res(TINFO, "Writing mtu '%s' was rejected as expected", mtu_str);
+	TST_EXP_EQ_LI(read_mtu(), before);
+}
+
+#endif /* SYS_NET_COMMON */
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 22/31] testcases: sysfs: Add sys_net03
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (20 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 21/31] testcases: sysfs: Add sys_net02 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 23/31] testcases: sysfs: Add sys_net04 Cyril Hrubis
                   ` (8 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/class/net/*/* files.

Similar to sys_net02 but checks bridge related files after briding a
virtual ethernet interface.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |   1 +
 testcases/kernel/sysfs/class/net/.gitignore  |   1 +
 testcases/kernel/sysfs/class/net/sys_net03.c | 144 +++++++++++++++++++
 3 files changed, 146 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net03.c

diff --git a/runtest/sysfs b/runtest/sysfs
index fee8a0b2b..b2cc0005c 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -17,3 +17,4 @@ sys_rtc01 sys_rtc01
 sys_thermal01 sys_thermal01
 sys_net01 sys_net01
 sys_net02 sys_net02
+sys_net03 sys_net03
diff --git a/testcases/kernel/sysfs/class/net/.gitignore b/testcases/kernel/sysfs/class/net/.gitignore
index b70d02158..8c3782a3b 100644
--- a/testcases/kernel/sysfs/class/net/.gitignore
+++ b/testcases/kernel/sysfs/class/net/.gitignore
@@ -1,2 +1,3 @@
 /sys_net01
 /sys_net02
+/sys_net03
diff --git a/testcases/kernel/sysfs/class/net/sys_net03.c b/testcases/kernel/sysfs/class/net/sys_net03.c
new file mode 100644
index 000000000..fcc5c5451
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net03.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Verifies that bridge port sysfs attributes correctly appear, hold sane
+ * values, and disappear again as a network device is enslaved to and
+ * released from a bridge.
+ *
+ * The test creates a bridge and a veth pair, then:
+ *
+ * - verifies /sys/class/net/<bridge>/brif/ is empty and
+ *   /sys/class/net/<port>/brport does not exist before enslaving
+ * - enslaves one end of the veth pair to the bridge
+ * - verifies /sys/class/net/<bridge>/brif/<port> appears and
+ *   /sys/class/net/<port>/brport/{state,priority,path_cost} hold sane values
+ * - verifies /sys/class/net/<bridge>/bridge/{stp_state,forward_delay} hold
+ *   sane values
+ * - releases the port from the bridge
+ * - verifies /sys/class/net/<bridge>/brif/ is empty and
+ *   /sys/class/net/<port>/brport does not exist again
+ *
+ * This needs root to create the bridge and veth devices.
+ */
+
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "tst_netdevice.h"
+#include "tst_sysfs_assert.h"
+
+#define BRNAME "ltp_br0"
+#define IFNAME1 "ltp_vethb1"
+#define IFNAME2 "ltp_vethb2"
+
+#define BR_PATH "/sys/class/net/" BRNAME
+#define PORT_PATH "/sys/class/net/" IFNAME1
+
+static int bridge_created;
+static int veth_created;
+
+static int count_dir_entries(const char *path)
+{
+	DIR *d;
+	struct dirent *ent;
+	int count = 0;
+
+	d = SAFE_OPENDIR(path);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		count++;
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	return count;
+}
+
+static void setup(void)
+{
+	NETDEV_ADD_DEVICE(BRNAME, "bridge");
+	bridge_created = 1;
+
+	CREATE_VETH_PAIR(IFNAME1, IFNAME2);
+	veth_created = 1;
+
+	NETDEV_SET_STATE(BRNAME, 1);
+	NETDEV_SET_STATE(IFNAME1, 1);
+	NETDEV_SET_STATE(IFNAME2, 1);
+}
+
+static void check_not_enslaved(const char *desc)
+{
+	int nports = count_dir_entries(BR_PATH "/brif");
+
+	tst_res(TINFO, "%s", desc);
+
+	TST_EXP_EQ_LI(nports, 0);
+
+	if (access(PORT_PATH "/brport", F_OK))
+		tst_res(TPASS, PORT_PATH "/brport does not exist");
+	else
+		tst_res(TFAIL, PORT_PATH "/brport unexpectedly exists");
+}
+
+static void check_enslaved(void)
+{
+	int nports = count_dir_entries(BR_PATH "/brif");
+
+	tst_res(TINFO, "port enslaved to bridge");
+
+	TST_EXP_EQ_LI(nports, 1);
+
+	if (!access(BR_PATH "/brif/" IFNAME1, F_OK))
+		tst_res(TPASS, BR_PATH "/brif/" IFNAME1 " exists");
+	else
+		tst_res(TFAIL, BR_PATH "/brif/" IFNAME1 " does not exist");
+
+	TST_SYSFS_ASSERT_RANGELL(0, 4, PORT_PATH "/brport/state");
+	TST_SYSFS_ASSERT_RANGELL(0, 255, PORT_PATH "/brport/priority");
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, PORT_PATH "/brport/path_cost");
+
+	TST_SYSFS_ASSERT_RANGELL(0, 2, BR_PATH "/bridge/stp_state");
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, BR_PATH "/bridge/forward_delay");
+}
+
+static void run(void)
+{
+	check_not_enslaved("before enslaving");
+
+	NETDEV_SET_MASTER(IFNAME1, BRNAME);
+	check_enslaved();
+
+	NETDEV_SET_MASTER(IFNAME1, NULL);
+	check_not_enslaved("after releasing");
+}
+
+static void cleanup(void)
+{
+	if (veth_created)
+		NETDEV_REMOVE_DEVICE(IFNAME1);
+
+	if (bridge_created)
+		NETDEV_REMOVE_DEVICE(BRNAME);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_VETH",
+		"CONFIG_BRIDGE",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 23/31] testcases: sysfs: Add sys_net04
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (21 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 22/31] testcases: sysfs: Add sys_net03 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 24/31] testcases: sysfs: Add sys_block_loop01 Cyril Hrubis
                   ` (7 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/class/net/*/* files.

Similar to sys_net02 but this time for TUN/TAP devices.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                |   1 +
 testcases/kernel/sysfs/class/net/.gitignore  |   1 +
 testcases/kernel/sysfs/class/net/sys_net04.c | 211 +++++++++++++++++++
 3 files changed, 213 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net04.c

diff --git a/runtest/sysfs b/runtest/sysfs
index b2cc0005c..290866349 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -18,3 +18,4 @@ sys_thermal01 sys_thermal01
 sys_net01 sys_net01
 sys_net02 sys_net02
 sys_net03 sys_net03
+sys_net04 sys_net04
diff --git a/testcases/kernel/sysfs/class/net/.gitignore b/testcases/kernel/sysfs/class/net/.gitignore
index 8c3782a3b..6e934b7c1 100644
--- a/testcases/kernel/sysfs/class/net/.gitignore
+++ b/testcases/kernel/sysfs/class/net/.gitignore
@@ -1,3 +1,4 @@
 /sys_net01
 /sys_net02
 /sys_net03
+/sys_net04
diff --git a/testcases/kernel/sysfs/class/net/sys_net04.c b/testcases/kernel/sysfs/class/net/sys_net04.c
new file mode 100644
index 000000000..a47cf44fa
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net04.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Verifies that the link state attributes exported under
+ * /sys/class/net/<iface>/ correctly mirror the attach/detach state of a TUN
+ * device's file descriptor, and that TUN/TAP-specific sysfs attributes hold
+ * sane values.
+ *
+ * Unlike veth, a TUN device has no peer network device: instead, its carrier
+ * is controlled by whether a userspace process currently has the device's
+ * /dev/net/tun file descriptor attached to it (tun_attach()/tun_detach() in
+ * the kernel call netif_carrier_on()/netif_carrier_off() respectively). The
+ * device is created persistent (TUNSETPERSIST) so that it survives the file
+ * descriptor being closed, which is what allows the attach/detach cycle to
+ * be exercised here without the interface disappearing. The test checks
+ * that closing and reopening the attachment is reflected in:
+ *
+ * - carrier - 1 while a file descriptor is attached, 0 while detached
+ * - operstate - "up" while attached, "down" while detached. Unlike veth,
+ *   this is plain "down" rather than "lowerlayerdown": the latter is only
+ *   reported when the carrier-down state is attributable to a registered
+ *   upper/lower netdevice relationship (a veth peer, a bond slave, a VLAN's
+ *   parent, ...), whereas a TUN device's carrier is driven by a userspace
+ *   file descriptor attachment, which is not a netdevice dependency at all.
+ * - carrier_up_count / carrier_down_count - incremented by exactly one on
+ *   each corresponding transition
+ * - carrier_changes - always equal to carrier_up_count + carrier_down_count
+ *
+ * These four attributes are generic net_device core statistics updated by
+ * netif_carrier_on()/off() regardless of the underlying driver, so the
+ * expected behavior mirrors the veth peer test in sys_net02.c, aside from
+ * the "down" vs "lowerlayerdown" difference above.
+ *
+ * The very first attach happens as part of the same TUNSETIFF call that
+ * registers the netdevice, before which netif_carrier_on() skips updating
+ * carrier_up_count/operstate for it. setup() therefore performs a throwaway
+ * detach/reattach cycle so that run() starts from a state where carrier
+ * tracking already behaves normally.
+ *
+ * The test also checks attributes that are not tied to the attach state:
+ *
+ * - mtu - directly writable via sysfs; a few valid values are written and
+ *   read back, and one syntactically invalid value is rejected (with the mtu
+ *   left unchanged)
+ * - tun_flags - reflects (at least) the IFF_TUN, IFF_NO_PI and IFF_PERSIST
+ *   bits the device was created/configured with, and never has IFF_TAP set
+ * - owner / group - default to -1 (no restriction) since neither
+ *   TUNSETOWNER nor TUNSETGROUP was used
+ * - a second, TAP-mode device is created solely to cross-check that type and
+ *   addr_len correctly differ between the two: a TUN device has no
+ *   link-layer header (type == ARPHRD_NONE, addr_len == 0), while a TAP
+ *   device behaves like Ethernet (type == ARPHRD_ETHER, addr_len == 6)
+ *
+ * This needs root to create the TUN/TAP devices.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#include "tst_test.h"
+#include "tst_netdevice.h"
+#include "tst_safe_file_ops.h"
+#include "tst_sysfs_assert.h"
+
+#define TUN_PATH "/dev/net/tun"
+
+#define IFNAME_TUN "ltp_tun0"
+#define IFNAME_TAP "ltp_tap0"
+#define IFACE_PATH "/sys/class/net/" IFNAME_TUN
+
+/* include/uapi/linux/if_arp.h, avoided pulling in the whole header. */
+#define ARPHRD_ETHER 1
+#define ARPHRD_NONE 0xFFFE
+
+static int tun_fd = -1;
+static int tap_fd = -1;
+static int tun_created;
+static int tap_created;
+
+static long read_mtu(void)
+{
+	return TST_SYSFS_READ_LI(IFACE_PATH "/mtu");
+}
+
+#include "sys_net_common.h"
+
+static int open_tun(const char *ifname, short flags)
+{
+	struct ifreq ifr = { .ifr_flags = flags };
+	int fd = SAFE_OPEN(TUN_PATH, O_RDWR);
+
+	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
+	SAFE_IOCTL(fd, TUNSETIFF, (void *)&ifr);
+
+	return fd;
+}
+
+static void setup(void)
+{
+	tun_fd = open_tun(IFNAME_TUN, IFF_TUN | IFF_NO_PI);
+	SAFE_IOCTL(tun_fd, TUNSETPERSIST, 1);
+	tun_created = 1;
+
+	/*
+	 * The initial attach above happens as part of the same TUNSETIFF call
+	 * that registers the netdevice, before which netif_carrier_on() skips
+	 * updating carrier_up_count/operstate for it. Detach and reattach
+	 * once here so run() starts from a state where carrier tracking
+	 * already behaves normally, see the top comment.
+	 */
+	SAFE_CLOSE(tun_fd);
+	NETDEV_SET_STATE(IFNAME_TUN, 1);
+	tun_fd = open_tun(IFNAME_TUN, IFF_TUN | IFF_NO_PI);
+
+	tap_fd = open_tun(IFNAME_TAP, IFF_TAP | IFF_NO_PI);
+	SAFE_IOCTL(tap_fd, TUNSETPERSIST, 1);
+	tap_created = 1;
+}
+
+static void check_tun_flags(void)
+{
+	unsigned long flags;
+
+	flags = TST_SYSFS_READ_LX(IFACE_PATH "/tun_flags");
+
+	tst_res(TINFO, "%s/tun_flags = 0x%lx", IFACE_PATH, flags);
+
+	TST_EXP_EQ_LI(flags & IFF_TUN, IFF_TUN);
+	TST_EXP_EQ_LI(flags & IFF_TAP, 0);
+	TST_EXP_EQ_LI(flags & IFF_NO_PI, IFF_NO_PI);
+	TST_EXP_EQ_LI(flags & IFF_PERSIST, IFF_PERSIST);
+}
+
+static void check_owner_group(void)
+{
+	TST_SYSFS_EXP_EQ_LI(-1, IFACE_PATH "/owner");
+	TST_SYSFS_EXP_EQ_LI(-1, IFACE_PATH "/group");
+}
+
+static void check_tun_vs_tap_type(void)
+{
+	TST_SYSFS_EXP_EQ_LI(ARPHRD_NONE, "/sys/class/net/" IFNAME_TUN "/type");
+	TST_SYSFS_EXP_EQ_LI(0, "/sys/class/net/" IFNAME_TUN "/addr_len");
+
+	TST_SYSFS_EXP_EQ_LI(ARPHRD_ETHER, "/sys/class/net/" IFNAME_TAP "/type");
+	TST_SYSFS_EXP_EQ_LI(6, "/sys/class/net/" IFNAME_TAP "/addr_len");
+}
+
+static void run(void)
+{
+	struct netdev_state s0, s1, s2;
+
+	read_state(&s0);
+	check_state(&s0, 1, "up", "tun attached and up");
+
+	SAFE_CLOSE(tun_fd);
+	read_state(&s1);
+	check_state(&s1, 0, "down", "tun detached");
+	check_state_delta(&s0, &s1, 0, 1, "detach transition");
+
+	tun_fd = open_tun(IFNAME_TUN, IFF_TUN | IFF_NO_PI);
+	read_state(&s2);
+	check_state(&s2, 1, "up", "tun reattached");
+	check_state_delta(&s1, &s2, 1, 0, "reattach transition");
+
+	check_mtu_valid(68);
+	check_mtu_valid(1500);
+	check_mtu_valid(9000);
+	check_mtu_valid(65535);
+	check_mtu_invalid("-1");
+	check_mtu_invalid("0");
+	check_mtu_invalid("67");
+	check_mtu_invalid("70000");
+
+	check_tun_flags();
+	check_owner_group();
+	check_tun_vs_tap_type();
+}
+
+static void cleanup(void)
+{
+	if (tun_fd >= 0)
+		SAFE_CLOSE(tun_fd);
+
+	if (tap_fd >= 0)
+		SAFE_CLOSE(tap_fd);
+
+	if (tun_created)
+		NETDEV_REMOVE_DEVICE(IFNAME_TUN);
+
+	if (tap_created)
+		NETDEV_REMOVE_DEVICE(IFNAME_TAP);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_TUN",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 24/31] testcases: sysfs: Add sys_block_loop01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (22 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 23/31] testcases: sysfs: Add sys_net04 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 25/31] testcases: sysfs: Add sys_block_queue01 Cyril Hrubis
                   ` (6 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/block/loop*/* files.

The test attaches loop device with different attributes and checks that
the sysfs files are correctly populated.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/block/.gitignore       |   1 +
 testcases/kernel/sysfs/block/Makefile         |   7 +
 .../kernel/sysfs/block/sys_block_loop01.c     | 320 ++++++++++++++++++
 4 files changed, 329 insertions(+)
 create mode 100644 testcases/kernel/sysfs/block/.gitignore
 create mode 100644 testcases/kernel/sysfs/block/Makefile
 create mode 100644 testcases/kernel/sysfs/block/sys_block_loop01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 290866349..26a229f03 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -19,3 +19,4 @@ sys_net01 sys_net01
 sys_net02 sys_net02
 sys_net03 sys_net03
 sys_net04 sys_net04
+sys_block_loop01 sys_block_loop01
diff --git a/testcases/kernel/sysfs/block/.gitignore b/testcases/kernel/sysfs/block/.gitignore
new file mode 100644
index 000000000..c64e99087
--- /dev/null
+++ b/testcases/kernel/sysfs/block/.gitignore
@@ -0,0 +1 @@
+/sys_block_loop01
diff --git a/testcases/kernel/sysfs/block/Makefile b/testcases/kernel/sysfs/block/Makefile
new file mode 100644
index 000000000..66e633f40
--- /dev/null
+++ b/testcases/kernel/sysfs/block/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/block/sys_block_loop01.c b/testcases/kernel/sysfs/block/sys_block_loop01.c
new file mode 100644
index 000000000..a99083a99
--- /dev/null
+++ b/testcases/kernel/sysfs/block/sys_block_loop01.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Verifies that the loop device sysfs attributes exported under
+ * /sys/block/loopN/loop/ correctly reflect the state of an attached backing
+ * file, and reset once the device is detached again.
+ *
+ * The test creates a plain file, attaches it to a free loop device and
+ * verifies that:
+ *
+ * - loop/backing_file matches the absolute path of the attached file
+ * - loop/offset and loop/sizelimit are 0 (defaults for a plain attach)
+ * - loop/autoclear, loop/partscan and loop/dio (when present) are booleans
+ *   and default to 0
+ * - the device's own size (in 512 byte sectors) matches the backing file
+ *   size
+ * - ro is 0 (attached read-write by default)
+ *
+ * After detaching the device it verifies that:
+ *
+ * - the device's size is back to 0
+ * - loop/backing_file no longer exists or reads back empty
+ *
+ * The device is then reconfigured via the LOOP_SET_STATUS64 ioctl (offset,
+ * sizelimit and the partscan flag) and it is verified that:
+ *
+ * - loop/offset and loop/sizelimit match the values that were set
+ * - loop/partscan reflects the requested flag
+ * - the device's size matches sizelimit/512, i.e. it is capped by sizelimit
+ *   rather than reflecting the whole backing file
+ *
+ * Note that LO_FLAGS_READ_ONLY is not exercised this way: on an already
+ * attached (read-write) device, LOOP_SET_STATUS64 does not actually flip the
+ * device to read-only, ro stays 0 regardless of the flag passed in. Read-only
+ * mode has to be requested at LOOP_SET_FD time instead, by opening the loop
+ * device (or the backing file) O_RDONLY before issuing the ioctl, which is
+ * exercised separately: the device is attached this way and it is verified
+ * that:
+ *
+ * - ro reads back as 1
+ * - the device genuinely rejects being opened or written to O_RDWR, not just
+ *   that the attribute says so
+ *
+ * ro turns out not to reset to 0 merely from detaching: it is a property of
+ * the whole gendisk, recalculated only on the next LOOP_SET_FD, so after
+ * detaching the device is reattached read-write and ro is checked to be 0
+ * then instead.
+ *
+ * Finally, autoclear is exercised for real rather than just checking that
+ * the attribute round-trips: the device is attached again, LO_FLAGS_AUTOCLEAR
+ * is set and the last open file descriptor on the device is closed without
+ * calling LOOP_CLR_FD explicitly. The kernel is expected to detach the
+ * device on its own (asynchronously, hence the test polls for it).
+ *
+ * This needs root to attach a loop device.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "tst_device.h"
+#include "tst_sysfs_assert.h"
+#include "lapi/loop.h"
+
+#define BACKING_FILE "sys_block_loop01.img"
+#define FILE_SIZE_MB 4
+#define RECONF_OFFSET 4096
+#define RECONF_SIZELIMIT (1024 * 1024)
+
+static char dev_path[64];
+static char loop_dir[64];
+static char abs_backing_file[PATH_MAX + sizeof(BACKING_FILE)];
+static int dev_num;
+static int attached;
+
+static void setup(void)
+{
+	char cwd[PATH_MAX];
+
+	dev_num = tst_find_free_loopdev(dev_path, sizeof(dev_path));
+	if (dev_num < 0)
+		tst_brk(TBROK, "Failed to find free loop device");
+
+	snprintf(loop_dir, sizeof(loop_dir), "/sys/block/loop%d", dev_num);
+
+	tst_prealloc_file(BACKING_FILE, 1024 * 1024, FILE_SIZE_MB);
+
+	SAFE_GETCWD(cwd, sizeof(cwd));
+	snprintf(abs_backing_file, sizeof(abs_backing_file), "%s/%s", cwd,
+		 BACKING_FILE);
+}
+
+static void check_backing_file(void)
+{
+	char actual[PATH_MAX] = "";
+
+	TST_SYSFS_READ_STR(actual, sizeof(actual), "%s/loop/backing_file",
+			   loop_dir);
+
+	TST_EXP_EQ_STR(actual, abs_backing_file);
+}
+
+static void check_attached(void)
+{
+	check_backing_file();
+
+	TST_SYSFS_EXP_EQ_LI(0, "%s/loop/offset", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(0, "%s/loop/sizelimit", loop_dir);
+
+	TST_SYSFS_ASSERT_BOOL("%s/loop/autoclear", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(0, "%s/loop/autoclear", loop_dir);
+
+	TST_SYSFS_ASSERT_BOOL("%s/loop/partscan", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(0, "%s/loop/partscan", loop_dir);
+
+	if (tst_sysfs_exists("%s/loop/dio", loop_dir)) {
+		TST_SYSFS_ASSERT_BOOL("%s/loop/dio", loop_dir);
+		TST_SYSFS_EXP_EQ_LI(0, "%s/loop/dio", loop_dir);
+	} else {
+		tst_res(TCONF, "%s/loop/dio does not exist", loop_dir);
+	}
+
+	TST_SYSFS_ASSERT_BOOL("/sys/block/loop%d/ro", dev_num);
+	TST_SYSFS_EXP_EQ_LI(0, "/sys/block/loop%d/ro", dev_num);
+
+	TST_SYSFS_EXP_EQ_LI((long)FILE_SIZE_MB * 1024 * 1024 / 512,
+			 "/sys/block/loop%d/size", dev_num);
+}
+
+static void check_detached(void)
+{
+	TST_SYSFS_EXP_EQ_LI(0, "/sys/block/loop%d/size", dev_num);
+
+	if (!tst_sysfs_exists("%s/loop/backing_file", loop_dir)) {
+		tst_res(TPASS, "backing_file no longer exists after detach");
+		return;
+	}
+
+	char actual[PATH_MAX] = "";
+
+	TST_SYSFS_READ_STR(actual, sizeof(actual), "%s/loop/backing_file",
+			   loop_dir);
+
+	if (actual[0] == '\0')
+		tst_res(TPASS, "backing_file is empty after detach");
+	else
+		tst_res(TFAIL, "backing_file = '%s' after detach, expected empty",
+			actual);
+}
+
+static void check_reconfigured(void)
+{
+	struct loop_info64 info = { 0 };
+	int fd;
+
+	fd = SAFE_OPEN(dev_path, O_RDWR);
+
+	info.lo_offset = RECONF_OFFSET;
+	info.lo_sizelimit = RECONF_SIZELIMIT;
+	info.lo_flags = LO_FLAGS_PARTSCAN;
+
+	SAFE_IOCTL(fd, LOOP_SET_STATUS64, &info);
+	SAFE_CLOSE(fd);
+
+	tst_res(TINFO,
+		"Reconfigured via LOOP_SET_STATUS64: offset=%d sizelimit=%d partscan",
+		RECONF_OFFSET, RECONF_SIZELIMIT);
+
+	TST_SYSFS_EXP_EQ_LI(RECONF_OFFSET, "%s/loop/offset", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(RECONF_SIZELIMIT, "%s/loop/sizelimit", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(1, "%s/loop/partscan", loop_dir);
+	TST_SYSFS_EXP_EQ_LI(RECONF_SIZELIMIT / 512, "/sys/block/loop%d/size",
+			 dev_num);
+}
+
+static void check_readonly_attach(void)
+{
+	int dev_fd, file_fd;
+
+	tst_res(TINFO,
+		"Checking a read-only attach (LOOP_SET_FD with an O_RDONLY device fd)");
+
+	dev_fd = SAFE_OPEN(dev_path, O_RDWR);
+	file_fd = SAFE_OPEN(BACKING_FILE, O_RDONLY);
+
+	SAFE_IOCTL(dev_fd, LOOP_SET_FD, file_fd);
+	SAFE_CLOSE(file_fd);
+	SAFE_CLOSE(dev_fd);
+	attached = 1;
+
+	TST_SYSFS_EXP_EQ_LI(1, "/sys/block/loop%d/ro", dev_num);
+
+	dev_fd = open(dev_path, O_RDWR);
+
+	if (dev_fd < 0) {
+		tst_res(TPASS,
+			"open(%s, O_RDWR) failed as expected on a read-only device: %s",
+			dev_path, tst_strerrno(errno));
+	} else {
+		char buf[512] = { 0 };
+		ssize_t written = write(dev_fd, buf, sizeof(buf));
+
+		if (written < 0) {
+			tst_res(TPASS,
+				"write() to a read-only loop device failed as expected: %s",
+				tst_strerrno(errno));
+		} else {
+			tst_res(TFAIL,
+				"write() to a read-only loop device unexpectedly succeeded (%zd bytes)",
+				written);
+		}
+
+		SAFE_CLOSE(dev_fd);
+	}
+
+	if (tst_detach_device(dev_path))
+		tst_brk(TBROK, "Failed to detach %s", dev_path);
+	attached = 0;
+
+	/*
+	 * ro does not reset to 0 merely from detaching, it is a property of
+	 * the whole gendisk that is only recalculated on the next LOOP_SET_FD,
+	 * so the device is reattached read-write here to verify that.
+	 */
+	if (tst_attach_device(dev_path, BACKING_FILE))
+		tst_brk(TBROK, "Failed to attach %s to %s", BACKING_FILE,
+			dev_path);
+	attached = 1;
+
+	TST_SYSFS_EXP_EQ_LI(0, "/sys/block/loop%d/ro", dev_num);
+
+	if (tst_detach_device(dev_path))
+		tst_brk(TBROK, "Failed to detach %s", dev_path);
+	attached = 0;
+}
+
+/* Reads back /sys/block/loopN/size, used to poll for autoclear below. */
+static long read_loop_size(void)
+{
+	return TST_SYSFS_READ_LI("/sys/block/loop%d/size", dev_num);
+}
+
+static void check_autoclear_behavior(void)
+{
+	struct loop_info64 info = { 0 };
+	long size;
+	int fd;
+
+	tst_res(TINFO,
+		"Checking that autoclear detaches the device on last close");
+
+	if (tst_attach_device(dev_path, BACKING_FILE)) {
+		tst_res(TFAIL, "Failed to attach %s to %s for autoclear check",
+			BACKING_FILE, dev_path);
+		return;
+	}
+	attached = 1;
+
+	fd = SAFE_OPEN(dev_path, O_RDWR);
+	info.lo_flags = LO_FLAGS_AUTOCLEAR;
+	SAFE_IOCTL(fd, LOOP_SET_STATUS64, &info);
+	SAFE_CLOSE(fd);
+
+	/* autoclear runs asynchronously off a kernel workqueue, poll for it */
+	size = TST_RETRY_FN_EXP_BACKOFF(read_loop_size(), TST_RETVAL_EQ0, 5);
+
+	if (size == 0) {
+		tst_res(TPASS, "Device auto-detached");
+		attached = 0;
+	} else {
+		tst_res(TFAIL, "Device did not auto-detach in time");
+		tst_detach_device(dev_path);
+		attached = 0;
+	}
+}
+
+static void run(void)
+{
+	if (tst_attach_device(dev_path, BACKING_FILE))
+		tst_brk(TBROK, "Failed to attach %s to %s", BACKING_FILE,
+			dev_path);
+	attached = 1;
+
+	check_attached();
+	check_reconfigured();
+
+	if (tst_detach_device(dev_path))
+		tst_brk(TBROK, "Failed to detach %s", dev_path);
+	attached = 0;
+
+	check_detached();
+
+	check_readonly_attach();
+	check_autoclear_behavior();
+}
+
+static void cleanup(void)
+{
+	if (attached)
+		tst_detach_device(dev_path);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_tmpdir = 1,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_BLK_DEV_LOOP",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 25/31] testcases: sysfs: Add sys_block_queue01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (23 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 24/31] testcases: sysfs: Add sys_block_loop01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 26/31] testcases: sysfs: Add sys_block_size01 Cyril Hrubis
                   ` (5 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/block/*/queue/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/block/.gitignore       |   1 +
 .../kernel/sysfs/block/sys_block_queue01.c    | 106 ++++++++++++++++++
 3 files changed, 108 insertions(+)
 create mode 100644 testcases/kernel/sysfs/block/sys_block_queue01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 26a229f03..f7d35e0c2 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -20,3 +20,4 @@ sys_net02 sys_net02
 sys_net03 sys_net03
 sys_net04 sys_net04
 sys_block_loop01 sys_block_loop01
+sys_block_queue01 sys_block_queue01
diff --git a/testcases/kernel/sysfs/block/.gitignore b/testcases/kernel/sysfs/block/.gitignore
index c64e99087..c7011b79f 100644
--- a/testcases/kernel/sysfs/block/.gitignore
+++ b/testcases/kernel/sysfs/block/.gitignore
@@ -1 +1,2 @@
 /sys_block_loop01
+/sys_block_queue01
diff --git a/testcases/kernel/sysfs/block/sys_block_queue01.c b/testcases/kernel/sysfs/block/sys_block_queue01.c
new file mode 100644
index 000000000..af31f6f64
--- /dev/null
+++ b/testcases/kernel/sysfs/block/sys_block_queue01.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the per block device request queue attributes exported
+ * under /sys/block/<dev>/queue/.
+ *
+ * For every block device that exports a request queue the test verifies that:
+ *
+ * - logical_block_size and physical_block_size are powers of two
+ * - logical_block_size <= physical_block_size
+ * - hw_sector_size equals logical_block_size
+ * - max_sectors_kb <= max_hw_sectors_kb
+ * - read_ahead_kb, nr_requests and discard_max_bytes are non-negative
+ * - nr_requests is greater than zero
+ * - rotational, add_random, iostats are boolean (0 or 1)
+ * - scheduler is a valid bracketed-choice file with exactly one active scheduler
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present, since the set of exported queue attributes differs between kernel
+ * versions and device types.
+ */
+
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_BLOCK "/sys/block"
+
+/* schedulers that have existed in mainline over time */
+static const char *const sched_allowed[] = {
+	"none", "noop", "deadline", "mq-deadline", "cfq",
+	"kyber", "bfq", "anticipatory", NULL
+};
+
+static void check_cross(const char *dev)
+{
+	TST_SYSFS_ASSERT_LE_SUFFIX("logical_block_size", "physical_block_size",
+				   SYS_BLOCK "/%s/queue/", dev);
+
+	TST_SYSFS_ASSERT_EQ_SUFFIX("logical_block_size", "hw_sector_size",
+				   SYS_BLOCK "/%s/queue/", dev);
+
+	TST_SYSFS_ASSERT_LE_SUFFIX("max_sectors_kb", "max_hw_sectors_kb",
+				   SYS_BLOCK "/%s/queue/", dev);
+}
+
+static void check_device(const char *dev)
+{
+	char sched[64];
+
+	tst_res(TINFO, "Checking block device '%s'", dev);
+
+	TST_SYSFS_ASSERT_POW2(SYS_BLOCK "/%s/queue/logical_block_size", dev);
+	TST_SYSFS_ASSERT_POW2(SYS_BLOCK "/%s/queue/physical_block_size", dev);
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX,
+			       SYS_BLOCK "/%s/queue/read_ahead_kb", dev);
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX,
+			       SYS_BLOCK "/%s/queue/nr_requests", dev);
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX,
+			       SYS_BLOCK "/%s/queue/discard_max_bytes", dev);
+
+	TST_SYSFS_ASSERT_BOOL(SYS_BLOCK "/%s/queue/rotational", dev);
+	TST_SYSFS_ASSERT_BOOL(SYS_BLOCK "/%s/queue/add_random", dev);
+	TST_SYSFS_ASSERT_BOOL(SYS_BLOCK "/%s/queue/iostats", dev);
+
+	TST_SYSFS_ASSERT_CHOICE(sched_allowed, sched, sizeof(sched),
+				SYS_BLOCK "/%s/queue/scheduler", dev);
+
+	check_cross(dev);
+}
+
+static void run(void)
+{
+	DIR *dir;
+	struct dirent *ent;
+	int found = 0;
+
+	dir = SAFE_OPENDIR(SYS_BLOCK);
+
+	while ((ent = SAFE_READDIR(dir))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		/* Only devices that export a request queue are of interest. */
+		if (!tst_sysfs_exists(SYS_BLOCK "/%s/queue", ent->d_name))
+			continue;
+
+		found = 1;
+		check_device(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(dir);
+
+	if (!found)
+		tst_res(TCONF, "No block device with a request queue found");
+}
+
+static struct tst_test test = {
+	.test_all = run,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 26/31] testcases: sysfs: Add sys_block_size01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (24 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 25/31] testcases: sysfs: Add sys_block_queue01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 27/31] testcases: sysfs: Add sys_hugepages01 Cyril Hrubis
                   ` (4 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/block/*/{ro, size} files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/block/.gitignore       |   1 +
 .../kernel/sysfs/block/sys_block_size01.c     | 102 ++++++++++++++++++
 3 files changed, 104 insertions(+)
 create mode 100644 testcases/kernel/sysfs/block/sys_block_size01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index f7d35e0c2..46593b82f 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -21,3 +21,4 @@ sys_net03 sys_net03
 sys_net04 sys_net04
 sys_block_loop01 sys_block_loop01
 sys_block_queue01 sys_block_queue01
+sys_block_size01 sys_block_size01
diff --git a/testcases/kernel/sysfs/block/.gitignore b/testcases/kernel/sysfs/block/.gitignore
index c7011b79f..9fd4a1d82 100644
--- a/testcases/kernel/sysfs/block/.gitignore
+++ b/testcases/kernel/sysfs/block/.gitignore
@@ -1,2 +1,3 @@
 /sys_block_loop01
 /sys_block_queue01
+/sys_block_size01
diff --git a/testcases/kernel/sysfs/block/sys_block_size01.c b/testcases/kernel/sysfs/block/sys_block_size01.c
new file mode 100644
index 000000000..c376d3a6c
--- /dev/null
+++ b/testcases/kernel/sysfs/block/sys_block_size01.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Cross-checks the block device size reported in sysfs against /proc/partitions.
+ *
+ * /sys/block/<dev>/size holds the device size in 512 byte sectors while
+ * /proc/partitions lists the size in 1024 byte blocks. For every whole block
+ * device the test verifies that:
+ *
+ * - the two sources agree, i.e. sysfs_size_in_sectors / 2 == proc_blocks
+ * - the ro (read-only) attribute is a boolean
+ *
+ * The test skips with TCONF when a device is not present in /proc/partitions
+ * (for example device-mapper devices that are only listed under /sys/block).
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_BLOCK "/sys/block"
+
+static int proc_partition_blocks(const char *dev, long long *blocks)
+{
+	FILE *f;
+	char line[4096];
+	int found = 0;
+
+	f = SAFE_FOPEN("/proc/partitions", "r");
+
+	while (fgets(line, sizeof(line), f)) {
+		long long nblocks;
+		char name[256];
+
+		if (sscanf(line, "%*u %*u %lld %255s", &nblocks, name) != 4)
+			continue;
+
+		if (!strcmp(name, dev)) {
+			*blocks = nblocks;
+			found = 1;
+			break;
+		}
+	}
+
+	SAFE_FCLOSE(f);
+
+	return found;
+}
+
+static void check_device(const char *dev)
+{
+	long long sectors, proc_blocks;
+
+	tst_res(TINFO, "Checking block device '%s'", dev);
+
+	TST_SYSFS_ASSERT_BOOL(SYS_BLOCK "/%s/ro", dev);
+
+	if (!proc_partition_blocks(dev, &proc_blocks)) {
+		tst_res(TCONF, "%s not listed in /proc/partitions", dev);
+		return;
+	}
+
+	sectors = TST_SYSFS_READ_LLI(SYS_BLOCK "/%s/size", dev);
+
+	/* sysfs size is in 512 byte sectors, /proc/partitions in 1024 byte blocks */
+	if (sectors / 2 == proc_blocks) {
+		tst_res(TPASS,
+			"%s: sysfs size %lld sectors matches /proc/partitions %lld blocks",
+			dev, sectors, proc_blocks);
+	} else {
+		tst_res(TFAIL,
+			"%s: sysfs size %lld sectors (/2 = %lld) != /proc/partitions %lld blocks",
+			dev, sectors, sectors / 2, proc_blocks);
+	}
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+
+	d = SAFE_OPENDIR(SYS_BLOCK);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		check_device(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 27/31] testcases: sysfs: Add sys_hugepages01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (25 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 26/31] testcases: sysfs: Add sys_block_size01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 28/31] testcases: sysfs: Add sys_hugepages02 Cyril Hrubis
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/mm/hugepages/hugepages-*/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/kernel/mm/Makefile     |   7 +
 .../sysfs/kernel/mm/hugepages/.gitignore      |   1 +
 .../kernel/sysfs/kernel/mm/hugepages/Makefile |   7 +
 .../kernel/mm/hugepages/sys_hugepages01.c     | 133 ++++++++++++++++++
 5 files changed, 149 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 46593b82f..a81fbc12e 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -22,3 +22,4 @@ sys_net04 sys_net04
 sys_block_loop01 sys_block_loop01
 sys_block_queue01 sys_block_queue01
 sys_block_size01 sys_block_size01
+sys_hugepages01 sys_hugepages01
diff --git a/testcases/kernel/sysfs/kernel/mm/Makefile b/testcases/kernel/sysfs/kernel/mm/Makefile
new file mode 100644
index 000000000..9bc26552c
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore b/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
new file mode 100644
index 000000000..1c66028fb
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
@@ -0,0 +1 @@
+/sys_hugepages01
diff --git a/testcases/kernel/sysfs/kernel/mm/hugepages/Makefile b/testcases/kernel/sysfs/kernel/mm/hugepages/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/hugepages/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages01.c b/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages01.c
new file mode 100644
index 000000000..13bcec3fc
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages01.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the hugepage pool attributes exported under
+ * /sys/kernel/mm/hugepages/.
+ *
+ * Each supported hugepage size has its own hugepages-<size>kB directory with a
+ * set of counters describing its pool. For each such directory the test
+ * verifies that:
+ *
+ * - the size encoded in the directory name is a power of two (in kB)
+ * - nr_hugepages, free_hugepages, resv_hugepages and surplus_hugepages are
+ *   all non-negative
+ * - free_hugepages <= nr_hugepages + surplus_hugepages (the free count cannot
+ *   exceed the total pool)
+ * - resv_hugepages <= free_hugepages (reserved pages are a subset of free)
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define HUGEPAGES "/sys/kernel/mm/hugepages"
+
+static int read_count(const char *dir, const char *attr, long *val)
+{
+	char path[PATH_MAX];
+
+	snprintf(path, sizeof(path), HUGEPAGES "/%s/%s", dir, attr);
+
+	if (access(path, F_OK))
+		return 1;
+
+	if (FILE_SCANF(path, "%ld", val))
+		return 1;
+
+	return 0;
+}
+
+static void check_size(const char *dir)
+{
+	long size;
+
+	if (sscanf(dir, "hugepages-%ldkB", &size) != 1) {
+		tst_res(TFAIL, "Malformed hugepage directory name '%s'", dir);
+		return;
+	}
+
+	if (size > 0 && !(size & (size - 1))) {
+		tst_res(TPASS, "%s: size %ld kB is a power of two", dir, size);
+	} else {
+		tst_res(TFAIL, "%s: size %ld kB is not a power of two",
+			dir, size);
+	}
+}
+
+static void check_counts(const char *dir)
+{
+	long nr, free, resv, surplus;
+
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, HUGEPAGES "/%s/nr_hugepages", dir);
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, HUGEPAGES "/%s/free_hugepages", dir);
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, HUGEPAGES "/%s/resv_hugepages", dir);
+	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX,
+			       HUGEPAGES "/%s/surplus_hugepages", dir);
+
+	if (read_count(dir, "nr_hugepages", &nr) ||
+	    read_count(dir, "free_hugepages", &free) ||
+	    read_count(dir, "surplus_hugepages", &surplus))
+		return;
+
+	if (free <= nr + surplus) {
+		tst_res(TPASS,
+			"%s: free_hugepages (%ld) <= nr_hugepages (%ld) + surplus_hugepages (%ld)",
+			dir, free, nr, surplus);
+	} else {
+		tst_res(TFAIL,
+			"%s: free_hugepages (%ld) > nr_hugepages (%ld) + surplus_hugepages (%ld)",
+			dir, free, nr, surplus);
+	}
+
+	if (read_count(dir, "resv_hugepages", &resv))
+		return;
+
+	if (resv <= free) {
+		tst_res(TPASS, "%s: resv_hugepages (%ld) <= free_hugepages (%ld)",
+			dir, resv, free);
+	} else {
+		tst_res(TFAIL, "%s: resv_hugepages (%ld) > free_hugepages (%ld)",
+			dir, resv, free);
+	}
+}
+
+static void run(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	d = SAFE_OPENDIR(HUGEPAGES);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "hugepages-", 10))
+			continue;
+
+		found = 1;
+		tst_res(TINFO, "Checking hugepage pool '%s'", ent->d_name);
+		check_size(ent->d_name);
+		check_counts(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found)
+		tst_res(TCONF, "No hugepage pools found");
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_HUGETLBFS=y",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 28/31] testcases: sysfs: Add sys_hugepages02
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (26 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 27/31] testcases: sysfs: Add sys_hugepages01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 29/31] testcases: sysfs: Add sys_ksm01 Cyril Hrubis
                   ` (2 subsequent siblings)
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/mm/hugepages/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../sysfs/kernel/mm/hugepages/.gitignore      |  1 +
 .../kernel/mm/hugepages/sys_hugepages02.c     | 84 +++++++++++++++++++
 3 files changed, 86 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages02.c

diff --git a/runtest/sysfs b/runtest/sysfs
index a81fbc12e..ec38d8b44 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -23,3 +23,4 @@ sys_block_loop01 sys_block_loop01
 sys_block_queue01 sys_block_queue01
 sys_block_size01 sys_block_size01
 sys_hugepages01 sys_hugepages01
+sys_hugepages02 sys_hugepages02
diff --git a/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore b/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
index 1c66028fb..1bebeb2c0 100644
--- a/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
+++ b/testcases/kernel/sysfs/kernel/mm/hugepages/.gitignore
@@ -1 +1,2 @@
 /sys_hugepages01
+/sys_hugepages02
diff --git a/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages02.c b/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages02.c
new file mode 100644
index 000000000..29d89d491
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/hugepages/sys_hugepages02.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Cross-checks the default hugepage pool in sysfs against /proc/meminfo.
+ *
+ * /proc/meminfo describes the default hugepage size pool, which corresponds to
+ * one of the hugepages-<size>kB directories under /sys/kernel/mm/hugepages/.
+ * The test verifies that for the default pool:
+ *
+ * - Hugepagesize in /proc/meminfo matches the default pool directory size
+ * - HugePages_Total matches nr_hugepages
+ * - HugePages_Free matches free_hugepages
+ * - HugePages_Rsvd matches resv_hugepages
+ * - HugePages_Surp matches surplus_hugepages
+ *
+ * The test skips with TCONF when hugepages are not available.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define HUGEPAGES "/sys/kernel/mm/hugepages"
+
+static void compare(const char *what, const char *meminfo_key, const char *attr,
+		    long hugepagesize)
+{
+	char path[PATH_MAX];
+	long meminfo_val, sysfs_val;
+
+	SAFE_FILE_LINES_SCANF("/proc/meminfo", meminfo_key, &meminfo_val);
+
+	snprintf(path, sizeof(path), HUGEPAGES "/hugepages-%ldkB/%s",
+		 hugepagesize, attr);
+	SAFE_FILE_SCANF(path, "%ld", &sysfs_val);
+
+	if (meminfo_val == sysfs_val) {
+		tst_res(TPASS, "%s: /proc/meminfo (%ld) == %s (%ld)",
+			what, meminfo_val, attr, sysfs_val);
+	} else {
+		tst_res(TFAIL, "%s: /proc/meminfo (%ld) != %s (%ld)",
+			what, meminfo_val, attr, sysfs_val);
+	}
+}
+
+static void do_test(void)
+{
+	long hugepagesize;
+
+	SAFE_FILE_LINES_SCANF("/proc/meminfo", "Hugepagesize: %ld",
+			      &hugepagesize);
+
+	if (!tst_sysfs_exists(HUGEPAGES "/hugepages-%ldkB", hugepagesize)) {
+		tst_res(TCONF,
+			"Default hugepage pool hugepages-%ldkB not found in sysfs",
+			hugepagesize);
+		return;
+	}
+
+	tst_res(TPASS,
+		"/proc/meminfo Hugepagesize (%ld kB) has a matching sysfs pool",
+		hugepagesize);
+
+	compare("HugePages_Total", "HugePages_Total: %ld",
+		"nr_hugepages", hugepagesize);
+	compare("HugePages_Free", "HugePages_Free: %ld",
+		"free_hugepages", hugepagesize);
+	compare("HugePages_Rsvd", "HugePages_Rsvd: %ld",
+		"resv_hugepages", hugepagesize);
+	compare("HugePages_Surp", "HugePages_Surp: %ld",
+		"surplus_hugepages", hugepagesize);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_HUGETLBFS=y",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 29/31] testcases: sysfs: Add sys_ksm01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (27 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 28/31] testcases: sysfs: Add sys_hugepages02 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 30/31] testcases: sysfs: Add sys_swap01 Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 31/31] testcases: sysfs: Add sys_thp01 Cyril Hrubis
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/mm/ksm/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../kernel/sysfs/kernel/mm/ksm/.gitignore     |  1 +
 testcases/kernel/sysfs/kernel/mm/ksm/Makefile |  7 ++
 .../kernel/sysfs/kernel/mm/ksm/sys_ksm01.c    | 64 +++++++++++++++++++
 4 files changed, 73 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/ksm/sys_ksm01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index ec38d8b44..08358ebc6 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -24,3 +24,4 @@ sys_block_queue01 sys_block_queue01
 sys_block_size01 sys_block_size01
 sys_hugepages01 sys_hugepages01
 sys_hugepages02 sys_hugepages02
+sys_ksm01 sys_ksm01
diff --git a/testcases/kernel/sysfs/kernel/mm/ksm/.gitignore b/testcases/kernel/sysfs/kernel/mm/ksm/.gitignore
new file mode 100644
index 000000000..92fa7e2c1
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/ksm/.gitignore
@@ -0,0 +1 @@
+/sys_ksm01
diff --git a/testcases/kernel/sysfs/kernel/mm/ksm/Makefile b/testcases/kernel/sysfs/kernel/mm/ksm/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/ksm/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/kernel/mm/ksm/sys_ksm01.c b/testcases/kernel/sysfs/kernel/mm/ksm/sys_ksm01.c
new file mode 100644
index 000000000..04eb8744e
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/ksm/sys_ksm01.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the Kernel Samepage Merging (KSM) attributes exported
+ * under /sys/kernel/mm/ksm/.
+ *
+ * The test verifies that:
+ *
+ * - run is one of 0 (stop), 1 (run) or 2 (unmerge)
+ * - merge_across_nodes and use_zero_pages are booleans
+ * - max_page_sharing is greater than zero
+ * - pages_to_scan and sleep_millisecs are non-negative
+ * - the page accounting counters (pages_shared, pages_sharing, pages_unshared,
+ *   pages_volatile, stable_node_chains, stable_node_dups) are non-negative
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present, since the set of KSM tunables differs between kernel versions.
+ */
+
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define KSM "/sys/kernel/mm/ksm"
+
+static const char *const nonneg_counters[] = {
+	"pages_shared",
+	"pages_sharing",
+	"pages_unshared",
+	"pages_volatile",
+	"stable_node_chains",
+	"stable_node_dups",
+	"pages_to_scan",
+	"sleep_millisecs",
+};
+
+static void do_test(void)
+{
+	unsigned int i;
+
+	/* 0 = stop, 1 = merge, 2 = unmerge */
+	TST_SYSFS_ASSERT_RANGELL(0, 2, KSM "/run");
+
+	TST_SYSFS_ASSERT_BOOL(KSM "/merge_across_nodes");
+	TST_SYSFS_ASSERT_BOOL(KSM "/use_zero_pages");
+
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, KSM "/max_page_sharing");
+
+	for (i = 0; i < ARRAY_SIZE(nonneg_counters); i++) {
+		TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, KSM "/%s",
+				       nonneg_counters[i]);
+	}
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_KSM=y",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 30/31] testcases: sysfs: Add sys_swap01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (28 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 29/31] testcases: sysfs: Add sys_ksm01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  2026-08-18 14:13 ` [LTP] [PATCH v1 31/31] testcases: sysfs: Add sys_thp01 Cyril Hrubis
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/mm/swap/vma_ra_enabled file.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../kernel/sysfs/kernel/mm/swap/.gitignore    |  1 +
 .../kernel/sysfs/kernel/mm/swap/Makefile      |  7 ++++
 .../sysfs/kernel/mm/swap/sys_mm_swap01.c      | 32 +++++++++++++++++++
 4 files changed, 41 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 08358ebc6..cb5b08018 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -25,3 +25,4 @@ sys_block_size01 sys_block_size01
 sys_hugepages01 sys_hugepages01
 sys_hugepages02 sys_hugepages02
 sys_ksm01 sys_ksm01
+sys_swap01 sys_swap01
diff --git a/testcases/kernel/sysfs/kernel/mm/swap/.gitignore b/testcases/kernel/sysfs/kernel/mm/swap/.gitignore
new file mode 100644
index 000000000..38895046b
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/swap/.gitignore
@@ -0,0 +1 @@
+/sys_mm_swap01
diff --git a/testcases/kernel/sysfs/kernel/mm/swap/Makefile b/testcases/kernel/sysfs/kernel/mm/swap/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/swap/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c b/testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c
new file mode 100644
index 000000000..1b5a622f5
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity check for /sys/kernel/mm/swap/vma_ra_enabled.
+ *
+ * Unlike most other boolean sysfs tunables which use 0/1, this one is
+ * formatted as the strings "true" or "false", so it needs its own check
+ * rather than TST_SYSFS_ASSERT_BOOL().
+ *
+ * The test skips with TCONF when the attribute is not present.
+ */
+
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define VMA_RA "/sys/kernel/mm/swap/vma_ra_enabled"
+
+static const char *const bool_str_allowed[] = {
+	"true", "false", NULL
+};
+
+static void do_test(void)
+{
+	TST_SYSFS_ASSERT_ONEOF(bool_str_allowed, VMA_RA);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [LTP] [PATCH v1 31/31] testcases: sysfs: Add sys_thp01
  2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
                   ` (29 preceding siblings ...)
  2026-08-18 14:13 ` [LTP] [PATCH v1 30/31] testcases: sysfs: Add sys_swap01 Cyril Hrubis
@ 2026-08-18 14:13 ` Cyril Hrubis
  30 siblings, 0 replies; 33+ messages in thread
From: Cyril Hrubis @ 2026-08-18 14:13 UTC (permalink / raw)
  To: ltp

A test for /sys/kernel/mm/transparent_hugepage/* files.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/sysfs                                 |  1 +
 .../kernel/mm/transparent_hugepage/.gitignore |  1 +
 .../kernel/mm/transparent_hugepage/Makefile   |  7 ++
 .../mm/transparent_hugepage/sys_thp01.c       | 86 +++++++++++++++++++
 4 files changed, 95 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/Makefile
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index cb5b08018..fd7a7124b 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -26,3 +26,4 @@ sys_hugepages01 sys_hugepages01
 sys_hugepages02 sys_hugepages02
 sys_ksm01 sys_ksm01
 sys_swap01 sys_swap01
+sys_thp01 sys_thp01
diff --git a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
new file mode 100644
index 000000000..bdb564dfe
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
@@ -0,0 +1 @@
+/sys_thp01
diff --git a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/Makefile b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir		?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp01.c b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp01.c
new file mode 100644
index 000000000..88200afa0
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp01.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the Transparent HugePage (THP) attributes exported under
+ * /sys/kernel/mm/transparent_hugepage/.
+ *
+ * The test verifies that:
+ *
+ * - enabled is a bracketed-choice file selecting one of always/madvise/never
+ * - defrag selects one of always/defer/defer+madvise/madvise/never
+ * - shmem_enabled selects one of always/within_size/advise/never/deny/force
+ * - use_zero_page is a boolean
+ * - hpage_pmd_size is a power of two and larger than the base page size
+ *
+ * All checks skip gracefully with TCONF when a particular attribute is not
+ * present, since the set of THP tunables differs between kernel versions.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define THP "/sys/kernel/mm/transparent_hugepage"
+
+static const char *const enabled_allowed[] = {
+	"always", "madvise", "never", NULL
+};
+
+static const char *const defrag_allowed[] = {
+	"always", "defer", "defer+madvise", "madvise", "never", NULL
+};
+
+static const char *const shmem_allowed[] = {
+	"always", "within_size", "advise", "never", "deny", "force", NULL
+};
+
+static void check_hpage_pmd_size(void)
+{
+	long size, pagesize;
+
+	if (access(THP "/hpage_pmd_size", F_OK)) {
+		tst_res(TCONF, THP "/hpage_pmd_size does not exist");
+		return;
+	}
+
+	TST_SYSFS_ASSERT_POW2(THP "/hpage_pmd_size");
+
+	size = TST_SYSFS_READ_LI(THP "/hpage_pmd_size");
+	pagesize = getpagesize();
+
+	if (size > pagesize) {
+		tst_res(TPASS, "hpage_pmd_size (%ld) > page size (%ld)",
+			size, pagesize);
+	} else {
+		tst_res(TFAIL, "hpage_pmd_size (%ld) <= page size (%ld)",
+			size, pagesize);
+	}
+}
+
+static void run(void)
+{
+	char sel[32];
+
+	TST_SYSFS_ASSERT_CHOICE(enabled_allowed, sel, sizeof(sel),
+				THP "/enabled");
+	TST_SYSFS_ASSERT_CHOICE(defrag_allowed, sel, sizeof(sel),
+				THP "/defrag");
+	TST_SYSFS_ASSERT_CHOICE(shmem_allowed, sel, sizeof(sel),
+				THP "/shmem_enabled");
+
+	TST_SYSFS_ASSERT_BOOL(THP "/use_zero_page");
+
+	check_hpage_pmd_size();
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_TRANSPARENT_HUGEPAGE=y",
+		NULL
+	},
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* Re: [LTP] lib: Add tst_sysfs_assert
  2026-08-18 14:12 ` [LTP] [PATCH v1 03/31] testcases: sysfs: Add sys_kernel01 Cyril Hrubis
@ 2026-08-18 14:54   ` linuxtestproject.agent
  0 siblings, 0 replies; 33+ messages in thread
From: linuxtestproject.agent @ 2026-08-18 14:54 UTC (permalink / raw)
  To: Cyril Hrubis; +Cc: ltp

Hi Cyril,

On Tue, 18 Aug 2026 16:12:41 +0200, Cyril Hrubis wrote:
> [PATCH 01/31] lib: Add tst_sysfs_assert

Most commit bodies in patches 1-20 and 25-31 only restate what the subject
already says. Could they explain why the coverage or API is needed? Patch 1
also has the typo "testscases", and the subjects of patches 14 and 15 do not
say that a test is being added.

--- [PATCH 1/31] ---

> 	size = read_file(path, buf, sizeof(buf));
> 	...
> 	map = SAFE_CALLOC(max_id / 8 + 1, 1);
> 	size = read_file(path, buf, sizeof(buf));
> 	...
> 	return parse_list(path, buf, size, count, max_id, map);

Could the list be read once, or could parse_list() receive and enforce the
allocated bitmap size? A dynamic list such as CPU online can gain a higher
ID between these reads, after which parse_list() writes beyond map.

--- [PATCH 6/31] ---

> 	TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_CPU "/online");
> 	...
> 	while (fgets(line, sizeof(line), f)) {
> 		if (sscanf(line, "cpu%u ", &cpu) == 1)
> 			cpu_count++;
> 	}

Could this retry unless the online list is stable around the /proc/stat
read? CPU hotplug between these independent snapshots produces different
counts even when both interfaces are correct.

--- [PATCH 7/31] ---

> 	snprintf(sub, sizeof(sub),
> 		 SYS_CPU "/cpu%d/topology/package_cpus_list", cpu);
> 	TST_SYSFS_ASSERT_LIST_SUBSET(sub, SYS_CPU "/online");

Could package_cpus_list be compared with possible/present instead? Topology
sibling masks can contain offline CPUs, so normal CPU hotplug makes this
assertion fail.

> 	for (cpu = 0; cpu <= max_id; cpu++)
> 		check_cpu_topology(cpu, poss_max_id);

Could this iterate the parsed online mask rather than every ID through its
maximum? CPU IDs can have offline holes.

> 	TST_SYSFS_ASSERT_RANGELL(0, poss_max_id,
> 			       SYS_CPU "/cpu%d/topology/physical_package_id",
> 			       cpu);

Could this accept -1? The generic topology implementation exports that
sentinel when an architecture does not provide a physical package ID.

--- [PATCH 9/31] ---

> static const char *const control_allowed[] = {
> 	"on", "off", "forceoff", "notsupported", "notimplemented", NULL
> };

Could this also parse the numeric control form? Linux 7.2 returns the active
thread count as a decimal value when partial SMT is enabled.

--- [PATCH 11/31] ---

> 	while ((ent = SAFE_READDIR(d))) {
> 		if (strncmp(ent->d_name, "clockevent", 10))
> 			continue;
>
> 		nclockevents++;
> 		check_clockevent(ent->d_name);
> 	}
> 	...
> 	if (nclockevents <= online_count) {

Could this validate only clockevent entries for online CPUs? Linux 7.2
registers clockeventN for every possible CPU, and current_device can be empty
for an offline CPU. Counting all entries against online CPUs therefore fails
on systems with offline CPUs.

--- [PATCH 12/31] ---

> 	d = SAFE_OPENDIR(ATA);

Could the class directory be checked before SAFE_OPENDIR()? Without libata,
its absence currently produces TBROK rather than the documented TCONF.

The same ordering appears for /sys/class/hwmon in patch 14,
/sys/class/leds in patch 15, /sys/class/wakeup in patch 16,
/sys/class/rtc in patch 17, and /sys/class/thermal in patch 18. Could those
also return TCONF when the corresponding class is unavailable?

--- [PATCH 13/31] ---

> 	min_bytes = TST_SYSFS_READ_LI(BDI "/%s/min_bytes", name);
> 	max_bytes = TST_SYSFS_READ_LI(BDI "/%s/max_bytes", name);

Could these use an unsigned 64-bit parser? Linux 7.2 exports both attributes
as u64, so valid values above LONG_MAX are rejected or misparsed, especially
on 32-bit systems.

--- [PATCH 14/31] ---

> 	TST_SYSFS_ASSERT_RANGELL(0, 1000000, HWMON "/%s/temp%d_input",
> 			       hwmon, nr);
> 	...
> 	if (min > max)
> 		tst_res(TFAIL, "temp%d_min (%ld) > temp%d_max (%ld)",
> 			nr, min, nr, max);

What kernel ABI guarantees these plausibility ranges and threshold
orderings? Hwmon values and writable thresholds are device-specific, and
the kernel does not enforce these policies. Valid hardware or configuration
can therefore fail the test.

--- [PATCH 16/31] ---

> 		TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, WAKEUP "/%s/%s",
> 				       name, counters[i]);

Could these counters use an unsigned-long parser? Linux 7.2 exports them with
"%lu", so a valid counter above LONG_MAX false-fails on 32-bit systems.

--- [PATCH 17/31] ---

> 	if (hctosys)
> 		check_system_time(rtc);
> 	...
> 	if (!hctosys_found)
> 		check_system_time("rtc0");

Could this comparison be removed or made informational? hctosys only records
that an RTC initialized system time at boot. NTP can subsequently correct
system time without updating the RTC, and rtc0 is not implicitly
synchronized when no hctosys attribute is set.

> 	TST_SYSFS_READ_STR(date, sizeof(date), RTC "/%s/date", rtc);
> 	TST_SYSFS_READ_STR(time, sizeof(time), RTC "/%s/time", rtc);

Could this use RTC_RD_TIME or verify matching date reads around the time
read? A midnight rollover between these files combines the previous date
with the next day's time and creates a false failure of about 24 hours.

--- [PATCH 19/31] ---

> /*
>  * Change the link-layer (MAC) address of an existing network device. Most
>  * drivers require the device to be administratively down for this to
>  * succeed.
>  */
> int tst_netdev_set_hwaddr(const char *file, const int lineno, int strict,
> 	const char *ifname, const void *addr, size_t addrlen);

Could the two new public APIs and their macros use kernel-doc, including
parameter documentation, so they are included in the generated C API
reference?

--- [PATCH 20/31] ---

>  * - carrier is a boolean (0 or 1) when readable
>  *
>  * carrier returns an error (EINVAL) when the interface is administratively
>  * down, which the test tolerates.

Could the promised carrier check be implemented, or could this claim be
removed? check_iface() currently validates only type, MTU, addr_len, address,
and operstate.

--- [PATCH 23/31] ---

> 	SAFE_CLOSE(fd);
> 	read_state(state);
> 	assert_state("down", 0, 0, state);
> 	...
> 	fd = open_tun();
> 	read_state(state);
> 	assert_state("up", 1, 1, state);

Could this poll for the expected operstate with a timeout? TUN updates
carrier synchronously, but netdev_state_change() schedules operstate updates
through linkwatch. These immediate reads can still observe the previous
operstate.

--- [PATCH 24/31] ---

> 	if (attached) {
> 		tst_res(TINFO, "Autoclear did not detach the loop device");
> 		tst_detach_device(loopdev);
> 		attached = 0;
> 	}

Could attached be cleared only when the fallback detach succeeds? If
tst_detach_device() fails, cleanup() skips the device and the system-wide
loop attachment is leaked.

--- [PATCH 25/31] ---

> 	TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX,
> 			       QUEUE "/%s/discard_max_bytes", dev);

Could discard_max_bytes use an unsigned 64-bit parser and range? The block
queue ABI exports an unsigned 64-bit byte count, which can exceed LONG_MAX
on 32-bit systems.

> static const char *const schedulers[] = {
> 	"none", "mq-deadline", "kyber", "bfq", NULL
> };

Could the scheduler check validate only the bracketed single-selection
format? Elevators are registered dynamically, so vendor or future scheduler
names outside this fixed list are valid.

--- [PATCH 26/31] ---

> 	if (sscanf(line, "%u %u %*u %*s", &proc_major, &proc_minor) != 4)
> 		continue;

Could this compare the return value with 2? Assignment-suppressed conversions
do not count, so every valid /proc/partitions line is currently skipped and
the size comparison never runs.

--- [PATCH 27/31] ---

> 	nr = TST_SYSFS_READ_LI(HUGEPAGES "/%s/nr_hugepages", name);
> 	free = TST_SYSFS_READ_LI(HUGEPAGES "/%s/free_hugepages", name);
> 	resv = TST_SYSFS_READ_LI(HUGEPAGES "/%s/resv_hugepages", name);
> 	surp = TST_SYSFS_READ_LI(HUGEPAGES "/%s/surplus_hugepages", name);

Could these checks retry unless surrounding pool counters are stable?
Allocation, reservation, or pool resizing between the separate reads can
violate the asserted relationships even though every individual kernel
snapshot is consistent.

--- [PATCH 28/31] ---

> 	proc_val = tst_read_meminfo(proc_name);
> 	sys_val = TST_SYSFS_READ_LI(HUGEPAGES "/%s", sys_name);
> 	TST_EXP_EQ_LI(proc_val, sys_val);

Could this compare stable snapshots or retry on a concurrent change?
Hugepage allocation, reservation, and pool resizing can occur between the
/proc and sysfs reads, producing a false mismatch.

--- [PATCH 29/31] ---

> 	for (i = 0; i < ARRAY_SIZE(nonneg_counters); i++) {
> 		TST_SYSFS_ASSERT_RANGELL(0, LONG_MAX, KSM "/%s",
> 				       nonneg_counters[i]);
> 	}

Could pages_to_scan and the page counters use parsers matching their unsigned
kernel types? Linux 7.2 exports pages_to_scan as unsigned int and the counters
as unsigned long, so valid values above LONG_MAX false-fail on 32-bit systems.

--- [PATCH 30/31] ---

> +sys_swap01 sys_swap01

> +++ b/testcases/kernel/sysfs/kernel/mm/swap/sys_mm_swap01.c

Could the source/binary and runtest names be made consistent? This builds
sys_mm_swap01, while runtest invokes the nonexistent sys_swap01.

Verdict - Needs revision

---
Note:

The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.

Regards,
LTP AI Reviewer

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 33+ messages in thread

end of thread, other threads:[~2026-08-18 14:54 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 14:12 [LTP] [PATCH v1 00/31] Add sysfs sanity tests Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 01/31] lib: Add tst_sysfs_assert Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 02/31] testcases: sysfs: Add sys_power01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 03/31] testcases: sysfs: Add sys_kernel01 Cyril Hrubis
2026-08-18 14:54   ` [LTP] lib: Add tst_sysfs_assert linuxtestproject.agent
2026-08-18 14:12 ` [LTP] [PATCH v1 04/31] testcases: sysfs: Add sys_clocksource01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 05/31] testcases: sysfs: Add sys_node01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 06/31] testcases: sysfs: Add sys_cpu_topology01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 07/31] testcases: sysfs: Add sys_cpu_topology02 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 08/31] testcases: sysfs: Add sys_cpu_vulnerabilities01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 09/31] testcases: sysfs: Add sys_cpu_smt01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 10/31] testcases: sysfs: Add sys_cpu_cache01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 11/31] testcases: sysfs: Add sys_clockevents01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 12/31] testcases: sysfs: Add sys_ata01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 13/31] testcases: sysfs: Add sys_bdi01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 14/31] testcases: sysfs: sys_hwmon01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 15/31] testcases: sysfs: sys_leds01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 16/31] testcases: sysfs: Add sys_wakeup01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 17/31] testcases: sysfs: Add sys_rtc01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 18/31] testcases: sysfs: Add sys_thermal01 Cyril Hrubis
2026-08-18 14:12 ` [LTP] [PATCH v1 19/31] tst_netdevice: Add two more helper macros Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 20/31] testcases: sysfs: Add sys_net01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 21/31] testcases: sysfs: Add sys_net02 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 22/31] testcases: sysfs: Add sys_net03 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 23/31] testcases: sysfs: Add sys_net04 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 24/31] testcases: sysfs: Add sys_block_loop01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 25/31] testcases: sysfs: Add sys_block_queue01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 26/31] testcases: sysfs: Add sys_block_size01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 27/31] testcases: sysfs: Add sys_hugepages01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 28/31] testcases: sysfs: Add sys_hugepages02 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 29/31] testcases: sysfs: Add sys_ksm01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 30/31] testcases: sysfs: Add sys_swap01 Cyril Hrubis
2026-08-18 14:13 ` [LTP] [PATCH v1 31/31] testcases: sysfs: Add sys_thp01 Cyril Hrubis

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.