On Mon, Mar 09, 2026 at 05:00:34AM -0700, Breno Leitao wrote:
> Add a shell-based selftest that exercises the full set of THP sysfs
> knobs: enabled (global and per-size anon), defrag, use_zero_page,
> hpage_pmd_size, shmem_enabled (global and per-size), shrink_underused,
> khugepaged/ tunables, and per-size stats files.
>
> Each writable knob is tested for valid writes, invalid-input rejection,
> idempotent writes, and mode transitions where applicable. All original
> values are saved before testing and restored afterwards.
>
> The test uses the kselftest KTAP framework (ktap_helpers.sh) for
> structured TAP 13 output, making results parseable by the kselftest
> harness. The test plan is printed at the end since the number of test
> points is dynamic (depends on available hugepage sizes and sysfs files).
>
> This is particularly useful for validating the refactoring of
> enabled_store() and anon_enabled_store() to use sysfs_match_string()
> and the new change_enabled()/change_anon_orders() helpers.
>
> Signed-off-by: Breno Leitao <[email protected]>

The test is broken locally for me, returning error code 127.

I do appreciate the effort here, so I'm sorry to push back negatively, but I
feel a bash script here is pretty janky, and frankly if any of these interfaces
were as broken as this it'd be a major failure that would surely get picked up
far sooner elsewhere.

So while I think this might be useful as a local test for your sysfs interface
changes, I don't think this is really suited to the mm selftests.

Andrew - can we drop this change please? Thanks!

Thanks, Lorenzo

> ---
> While working on the THP sysfs interface, I noticed there were no
> existing tests covering this functionality. This patch adds test
> coverage that may be useful for upstream !?
> ---
>  tools/testing/selftests/mm/Makefile          |   1 +
>  tools/testing/selftests/mm/run_vmtests.sh    |   2 +
>  tools/testing/selftests/mm/thp_sysfs_test.sh | 666 
> +++++++++++++++++++++++++++
>  3 files changed, 669 insertions(+)
>
> diff --git a/tools/testing/selftests/mm/Makefile 
> b/tools/testing/selftests/mm/Makefile
> index 7a5de4e9bf520..90fcca53a561b 100644
> --- a/tools/testing/selftests/mm/Makefile
> +++ b/tools/testing/selftests/mm/Makefile
> @@ -180,6 +180,7 @@ TEST_FILES += charge_reserved_hugetlb.sh
>  TEST_FILES += hugetlb_reparenting_test.sh
>  TEST_FILES += test_page_frag.sh
>  TEST_FILES += run_vmtests.sh
> +TEST_FILES += thp_sysfs_test.sh
>
>  # required by charge_reserved_hugetlb.sh
>  TEST_FILES += write_hugetlb_memory.sh
> diff --git a/tools/testing/selftests/mm/run_vmtests.sh 
> b/tools/testing/selftests/mm/run_vmtests.sh
> index afdcfd0d7cef7..9e0e2089214a3 100755
> --- a/tools/testing/selftests/mm/run_vmtests.sh
> +++ b/tools/testing/selftests/mm/run_vmtests.sh
> @@ -491,6 +491,8 @@ CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem
>
>  CATEGORY="thp" run_test ./transhuge-stress -d 20
>
> +CATEGORY="thp" run_test ./thp_sysfs_test.sh
> +
>  # Try to create XFS if not provided
>  if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then
>      if [ "${HAVE_HUGEPAGES}" = "1" ]; then
> diff --git a/tools/testing/selftests/mm/thp_sysfs_test.sh 
> b/tools/testing/selftests/mm/thp_sysfs_test.sh
> new file mode 100755
> index 0000000000000..407f306a0989f
> --- /dev/null
> +++ b/tools/testing/selftests/mm/thp_sysfs_test.sh
> @@ -0,0 +1,666 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Test THP sysfs interface.
> +#
> +# Exercises the full set of THP sysfs knobs: enabled (global and
> +# per-size anon), defrag, use_zero_page, hpage_pmd_size, shmem_enabled
> +# (global and per-size), shrink_underused, khugepaged/ tunables, and
> +# per-size stats files.  Each writable knob is tested for valid writes,
> +# invalid-input rejection, idempotent writes, and mode transitions
> +# where applicable.  All original values are saved before testing and
> +# restored afterwards.
> +#
> +# Author: Breno Leitao <[email protected]>
> +
> +DIR="$(dirname "$(readlink -f "$0")")"
> +. "${DIR}"/../kselftest/ktap_helpers.sh
> +
> +THP_SYSFS=/sys/kernel/mm/transparent_hugepage
> +
> +# Read the currently active mode from a sysfs enabled file.
> +# The active mode is enclosed in brackets, e.g. "always [madvise] never"
> +get_active_mode() {
> +     local path="$1"
> +     local content
> +
> +     content=$(cat "$path")
> +     echo "$content" | grep -o '\[.*\]' | tr -d '[]'
> +}
> +
> +# Test that writing a mode and reading it back gives the expected result.
> +test_mode() {
> +     local path="$1"
> +     local mode="$2"
> +     local label="$3"
> +     local active
> +
> +     if ! echo "$mode" > "$path" 2>/dev/null; then
> +             ktap_test_fail "$label: write '$mode'"
> +             return
> +     fi
> +
> +     active=$(get_active_mode "$path")
> +     if [ "$active" = "$mode" ]; then
> +             ktap_test_pass "$label: write '$mode'"
> +     else
> +             ktap_test_fail "$label: write '$mode', read back '$active'"
> +     fi
> +}
> +
> +# Test that writing an invalid mode is rejected.
> +test_invalid() {
> +     local path="$1"
> +     local mode="$2"
> +     local label="$3"
> +     local saved
> +
> +     saved=$(get_active_mode "$path")
> +
> +     if echo "$mode" > "$path" 2>/dev/null; then
> +             # Write succeeded -- check if mode actually changed (it 
> shouldn't)
> +             local active
> +             active=$(get_active_mode "$path")
> +             if [ "$active" = "$saved" ]; then
> +                     # Some shells don't propagate the error, but mode 
> unchanged
> +                     ktap_test_pass "$label: reject '$mode'"
> +             else
> +                     ktap_test_fail "$label: '$mode' should have been 
> rejected but mode changed to '$active'"
> +             fi
> +     else
> +             ktap_test_pass "$label: reject '$mode'"
> +     fi
> +}
> +
> +# Test that writing the same mode twice doesn't crash or change state.
> +test_idempotent() {
> +     local path="$1"
> +     local mode="$2"
> +     local label="$3"
> +
> +     echo "$mode" > "$path" 2>/dev/null
> +     echo "$mode" > "$path" 2>/dev/null
> +     local active
> +     active=$(get_active_mode "$path")
> +     if [ "$active" = "$mode" ]; then
> +             ktap_test_pass "$label: idempotent '$mode'"
> +     else
> +             ktap_test_fail "$label: idempotent '$mode', got '$active'"
> +     fi
> +}
> +
> +# Write a numeric value, read it back, verify match.
> +test_numeric() {
> +     local path="$1"
> +     local value="$2"
> +     local label="$3"
> +     local readback
> +
> +     if ! echo "$value" > "$path" 2>/dev/null; then
> +             ktap_test_fail "$label: write '$value'"
> +             return
> +     fi
> +
> +     readback=$(cat "$path" 2>/dev/null)
> +     if [ "$readback" = "$value" ]; then
> +             ktap_test_pass "$label: write '$value'"
> +     else
> +             ktap_test_fail "$label: write '$value', read back '$readback'"
> +     fi
> +}
> +
> +# Verify that an out-of-range or invalid numeric value is rejected.
> +test_numeric_invalid() {
> +     local path="$1"
> +     local value="$2"
> +     local label="$3"
> +     local saved readback
> +
> +     saved=$(cat "$path" 2>/dev/null)
> +
> +     if echo "$value" > "$path" 2>/dev/null; then
> +             readback=$(cat "$path" 2>/dev/null)
> +             if [ "$readback" = "$saved" ]; then
> +                     ktap_test_pass "$label: reject '$value'"
> +             else
> +                     ktap_test_fail "$label: '$value' should have been 
> rejected but value changed to '$readback'"
> +             fi
> +     else
> +             ktap_test_pass "$label: reject '$value'"
> +     fi
> +}
> +
> +# Verify a read-only file: readable, returns a numeric value, rejects writes.
> +test_readonly() {
> +     local path="$1"
> +     local label="$2"
> +     local val
> +
> +     val=$(cat "$path" 2>/dev/null)
> +     if [ -z "$val" ]; then
> +             ktap_test_fail "$label: read returned empty"
> +             return
> +     fi
> +
> +     if ! echo "$val" | grep -qE '^[0-9]+$'; then
> +             ktap_test_fail "$label: expected numeric, got '$val'"
> +             return
> +     fi
> +
> +     if echo "0" > "$path" 2>/dev/null; then
> +             local after
> +             after=$(cat "$path" 2>/dev/null)
> +             if [ "$after" = "$val" ]; then
> +                     ktap_test_pass "$label: read-only (value=$val)"
> +             else
> +                     ktap_test_fail "$label: write should have been rejected 
> but value changed"
> +             fi
> +     else
> +             ktap_test_pass "$label: read-only (value=$val)"
> +     fi
> +}
> +
> +# --- Precondition checks ---
> +
> +ktap_print_header
> +
> +if [ ! -d "$THP_SYSFS" ]; then
> +     ktap_skip_all "THP sysfs not found at $THP_SYSFS"
> +     exit "$KSFT_SKIP"
> +fi
> +
> +if [ "$(id -u)" -ne 0 ]; then
> +     ktap_skip_all "must be run as root"
> +     exit "$KSFT_SKIP"
> +fi
> +
> +# --- Test global THP enabled ---
> +
> +GLOBAL_ENABLED="$THP_SYSFS/enabled"
> +
> +if [ ! -f "$GLOBAL_ENABLED" ]; then
> +     ktap_test_skip "global enabled file not found"
> +else
> +     ktap_print_msg "Testing global THP enabled ($GLOBAL_ENABLED)"
> +
> +     # Save current setting
> +     saved_global=$(get_active_mode "$GLOBAL_ENABLED")
> +
> +     # Valid modes for global
> +     test_mode "$GLOBAL_ENABLED" "always" "global"
> +     test_mode "$GLOBAL_ENABLED" "madvise" "global"
> +     test_mode "$GLOBAL_ENABLED" "never" "global"
> +
> +     # "inherit" is not valid for global THP
> +     test_invalid "$GLOBAL_ENABLED" "inherit" "global"
> +
> +     # Invalid strings
> +     test_invalid "$GLOBAL_ENABLED" "bogus" "global"
> +     test_invalid "$GLOBAL_ENABLED" "" "global (empty)"
> +
> +     # Idempotent writes
> +     test_idempotent "$GLOBAL_ENABLED" "always" "global"
> +     test_idempotent "$GLOBAL_ENABLED" "never" "global"
> +
> +     # Restore
> +     echo "$saved_global" > "$GLOBAL_ENABLED" 2>/dev/null
> +fi
> +
> +# --- Test global defrag ---
> +
> +GLOBAL_DEFRAG="$THP_SYSFS/defrag"
> +
> +if [ ! -f "$GLOBAL_DEFRAG" ]; then
> +     ktap_test_skip "defrag file not found"
> +else
> +     ktap_print_msg "Testing global THP defrag ($GLOBAL_DEFRAG)"
> +
> +     saved_defrag=$(get_active_mode "$GLOBAL_DEFRAG")
> +
> +     # Valid modes
> +     test_mode "$GLOBAL_DEFRAG" "always" "defrag"
> +     test_mode "$GLOBAL_DEFRAG" "defer" "defrag"
> +     test_mode "$GLOBAL_DEFRAG" "defer+madvise" "defrag"
> +     test_mode "$GLOBAL_DEFRAG" "madvise" "defrag"
> +     test_mode "$GLOBAL_DEFRAG" "never" "defrag"
> +
> +     # Invalid
> +     test_invalid "$GLOBAL_DEFRAG" "bogus" "defrag"
> +     test_invalid "$GLOBAL_DEFRAG" "" "defrag (empty)"
> +     test_invalid "$GLOBAL_DEFRAG" "inherit" "defrag"
> +
> +     # Idempotent
> +     test_idempotent "$GLOBAL_DEFRAG" "always" "defrag"
> +     test_idempotent "$GLOBAL_DEFRAG" "never" "defrag"
> +
> +     # Mode transitions: cycle through all 5
> +     echo "always" > "$GLOBAL_DEFRAG"
> +     test_mode "$GLOBAL_DEFRAG" "defer" "defrag (always->defer)"
> +     test_mode "$GLOBAL_DEFRAG" "defer+madvise" "defrag 
> (defer->defer+madvise)"
> +     test_mode "$GLOBAL_DEFRAG" "madvise" "defrag (defer+madvise->madvise)"
> +     test_mode "$GLOBAL_DEFRAG" "never" "defrag (madvise->never)"
> +     test_mode "$GLOBAL_DEFRAG" "always" "defrag (never->always)"
> +
> +     # Restore
> +     echo "$saved_defrag" > "$GLOBAL_DEFRAG" 2>/dev/null
> +fi
> +
> +# --- Test use_zero_page ---
> +
> +USE_ZERO_PAGE="$THP_SYSFS/use_zero_page"
> +
> +if [ ! -f "$USE_ZERO_PAGE" ]; then
> +     ktap_test_skip "use_zero_page file not found"
> +else
> +     ktap_print_msg "Testing use_zero_page ($USE_ZERO_PAGE)"
> +
> +     saved_uzp=$(cat "$USE_ZERO_PAGE" 2>/dev/null)
> +
> +     # Valid values
> +     test_numeric "$USE_ZERO_PAGE" "0" "use_zero_page"
> +     test_numeric "$USE_ZERO_PAGE" "1" "use_zero_page"
> +
> +     # Invalid values
> +     test_numeric_invalid "$USE_ZERO_PAGE" "2" "use_zero_page"
> +     test_numeric_invalid "$USE_ZERO_PAGE" "-1" "use_zero_page"
> +     test_numeric_invalid "$USE_ZERO_PAGE" "bogus" "use_zero_page"
> +
> +     # Idempotent
> +     echo "1" > "$USE_ZERO_PAGE" 2>/dev/null
> +     test_numeric "$USE_ZERO_PAGE" "1" "use_zero_page (idempotent)"
> +     echo "0" > "$USE_ZERO_PAGE" 2>/dev/null
> +     test_numeric "$USE_ZERO_PAGE" "0" "use_zero_page (idempotent)"
> +
> +     # Restore
> +     echo "$saved_uzp" > "$USE_ZERO_PAGE" 2>/dev/null
> +fi
> +
> +# --- Test hpage_pmd_size ---
> +
> +HPAGE_PMD_SIZE_FILE="$THP_SYSFS/hpage_pmd_size"
> +
> +if [ ! -f "$HPAGE_PMD_SIZE_FILE" ]; then
> +     ktap_test_skip "hpage_pmd_size file not found"
> +else
> +     ktap_print_msg "Testing hpage_pmd_size ($HPAGE_PMD_SIZE_FILE)"
> +
> +     test_readonly "$HPAGE_PMD_SIZE_FILE" "hpage_pmd_size"
> +fi
> +
> +# --- Test global shmem_enabled ---
> +
> +SHMEM_ENABLED="$THP_SYSFS/shmem_enabled"
> +
> +if [ ! -f "$SHMEM_ENABLED" ]; then
> +     ktap_test_skip "shmem_enabled file not found (CONFIG_SHMEM not set?)"
> +else
> +     ktap_print_msg "Testing global shmem_enabled ($SHMEM_ENABLED)"
> +
> +     saved_shmem=$(get_active_mode "$SHMEM_ENABLED")
> +
> +     # Valid modes
> +     test_mode "$SHMEM_ENABLED" "always" "shmem_enabled"
> +     test_mode "$SHMEM_ENABLED" "within_size" "shmem_enabled"
> +     test_mode "$SHMEM_ENABLED" "advise" "shmem_enabled"
> +     test_mode "$SHMEM_ENABLED" "never" "shmem_enabled"
> +     test_mode "$SHMEM_ENABLED" "deny" "shmem_enabled"
> +     test_mode "$SHMEM_ENABLED" "force" "shmem_enabled"
> +
> +     # Invalid
> +     test_invalid "$SHMEM_ENABLED" "bogus" "shmem_enabled"
> +     test_invalid "$SHMEM_ENABLED" "inherit" "shmem_enabled"
> +     test_invalid "$SHMEM_ENABLED" "" "shmem_enabled (empty)"
> +
> +     # Idempotent
> +     test_idempotent "$SHMEM_ENABLED" "always" "shmem_enabled"
> +     test_idempotent "$SHMEM_ENABLED" "never" "shmem_enabled"
> +
> +     # Mode transitions: cycle through all 6
> +     echo "always" > "$SHMEM_ENABLED"
> +     test_mode "$SHMEM_ENABLED" "within_size" "shmem_enabled 
> (always->within_size)"
> +     test_mode "$SHMEM_ENABLED" "advise" "shmem_enabled 
> (within_size->advise)"
> +     test_mode "$SHMEM_ENABLED" "never" "shmem_enabled (advise->never)"
> +     test_mode "$SHMEM_ENABLED" "deny" "shmem_enabled (never->deny)"
> +     test_mode "$SHMEM_ENABLED" "force" "shmem_enabled (deny->force)"
> +     test_mode "$SHMEM_ENABLED" "always" "shmem_enabled (force->always)"
> +
> +     # Restore
> +     echo "$saved_shmem" > "$SHMEM_ENABLED" 2>/dev/null
> +fi
> +
> +# --- Test shrink_underused ---
> +
> +SHRINK_UNDERUSED="$THP_SYSFS/shrink_underused"
> +
> +if [ ! -f "$SHRINK_UNDERUSED" ]; then
> +     ktap_test_skip "shrink_underused file not found"
> +else
> +     ktap_print_msg "Testing shrink_underused ($SHRINK_UNDERUSED)"
> +
> +     saved_shrink=$(cat "$SHRINK_UNDERUSED" 2>/dev/null)
> +
> +     # Valid values
> +     test_numeric "$SHRINK_UNDERUSED" "0" "shrink_underused"
> +     test_numeric "$SHRINK_UNDERUSED" "1" "shrink_underused"
> +
> +     # Invalid values
> +     test_numeric_invalid "$SHRINK_UNDERUSED" "2" "shrink_underused"
> +     test_numeric_invalid "$SHRINK_UNDERUSED" "bogus" "shrink_underused"
> +
> +     # Restore
> +     echo "$saved_shrink" > "$SHRINK_UNDERUSED" 2>/dev/null
> +fi
> +
> +# --- Test per-size anon THP enabled ---
> +
> +found_anon=0
> +for dir in "$THP_SYSFS"/hugepages-*; do
> +     [ -d "$dir" ] || continue
> +
> +     ANON_ENABLED="$dir/enabled"
> +     [ -f "$ANON_ENABLED" ] || continue
> +
> +     found_anon=1
> +     size=$(basename "$dir")
> +     ktap_print_msg "Testing per-size anon THP enabled ($size)"
> +
> +     # Save current setting
> +     saved_anon=$(get_active_mode "$ANON_ENABLED")
> +
> +     # Valid modes for per-size anon (includes inherit)
> +     test_mode "$ANON_ENABLED" "always" "$size"
> +     test_mode "$ANON_ENABLED" "inherit" "$size"
> +     test_mode "$ANON_ENABLED" "madvise" "$size"
> +     test_mode "$ANON_ENABLED" "never" "$size"
> +
> +     # Invalid strings
> +     test_invalid "$ANON_ENABLED" "bogus" "$size"
> +
> +     # Idempotent writes
> +     test_idempotent "$ANON_ENABLED" "always" "$size"
> +     test_idempotent "$ANON_ENABLED" "inherit" "$size"
> +     test_idempotent "$ANON_ENABLED" "never" "$size"
> +
> +     # Mode transitions: verify each mode clears the others
> +     echo "always" > "$ANON_ENABLED"
> +     test_mode "$ANON_ENABLED" "madvise" "$size (always->madvise)"
> +     test_mode "$ANON_ENABLED" "inherit" "$size (madvise->inherit)"
> +     test_mode "$ANON_ENABLED" "never" "$size (inherit->never)"
> +     test_mode "$ANON_ENABLED" "always" "$size (never->always)"
> +
> +     # Restore
> +     echo "$saved_anon" > "$ANON_ENABLED" 2>/dev/null
> +
> +     # Only test one size in detail to keep output manageable,
> +     # but do a quick smoke test on the rest
> +     break
> +done
> +
> +if [ $found_anon -eq 0 ]; then
> +     ktap_test_skip "no per-size anon THP directories found"
> +fi
> +
> +# Quick smoke test: all other sizes accept valid modes
> +first=1
> +for dir in "$THP_SYSFS"/hugepages-*; do
> +     [ -d "$dir" ] || continue
> +     ANON_ENABLED="$dir/enabled"
> +     [ -f "$ANON_ENABLED" ] || continue
> +
> +     # Skip the first one (already tested in detail)
> +     if [ $first -eq 1 ]; then
> +             first=0
> +             continue
> +     fi
> +
> +     size=$(basename "$dir")
> +     saved=$(get_active_mode "$ANON_ENABLED")
> +
> +     smoke_failed=0
> +     for mode in always inherit madvise never; do
> +             echo "$mode" > "$ANON_ENABLED" 2>/dev/null
> +             active=$(get_active_mode "$ANON_ENABLED")
> +             if [ "$active" != "$mode" ]; then
> +                     ktap_test_fail "$size: smoke test '$mode' got '$active'"
> +                     smoke_failed=1
> +                     break
> +             fi
> +     done
> +     [ $smoke_failed -eq 0 ] && ktap_test_pass "$size: smoke test all modes"
> +
> +     echo "$saved" > "$ANON_ENABLED" 2>/dev/null
> +done
> +
> +# --- Test per-size shmem_enabled ---
> +
> +found_shmem=0
> +for dir in "$THP_SYSFS"/hugepages-*; do
> +     [ -d "$dir" ] || continue
> +
> +     SHMEM_SIZE_ENABLED="$dir/shmem_enabled"
> +     [ -f "$SHMEM_SIZE_ENABLED" ] || continue
> +
> +     found_shmem=1
> +     size=$(basename "$dir")
> +     ktap_print_msg "Testing per-size shmem_enabled ($size)"
> +
> +     # Save current setting
> +     saved_shmem_size=$(get_active_mode "$SHMEM_SIZE_ENABLED")
> +
> +     # Valid modes for per-size shmem
> +     test_mode "$SHMEM_SIZE_ENABLED" "always" "$size shmem"
> +     test_mode "$SHMEM_SIZE_ENABLED" "inherit" "$size shmem"
> +     test_mode "$SHMEM_SIZE_ENABLED" "within_size" "$size shmem"
> +     test_mode "$SHMEM_SIZE_ENABLED" "advise" "$size shmem"
> +     test_mode "$SHMEM_SIZE_ENABLED" "never" "$size shmem"
> +
> +     # Invalid: deny and force are not valid for per-size
> +     test_invalid "$SHMEM_SIZE_ENABLED" "bogus" "$size shmem"
> +     test_invalid "$SHMEM_SIZE_ENABLED" "deny" "$size shmem"
> +     test_invalid "$SHMEM_SIZE_ENABLED" "force" "$size shmem"
> +
> +     # Mode transitions
> +     echo "always" > "$SHMEM_SIZE_ENABLED"
> +     test_mode "$SHMEM_SIZE_ENABLED" "inherit" "$size shmem 
> (always->inherit)"
> +     test_mode "$SHMEM_SIZE_ENABLED" "within_size" "$size shmem 
> (inherit->within_size)"
> +     test_mode "$SHMEM_SIZE_ENABLED" "advise" "$size shmem 
> (within_size->advise)"
> +     test_mode "$SHMEM_SIZE_ENABLED" "never" "$size shmem (advise->never)"
> +     test_mode "$SHMEM_SIZE_ENABLED" "always" "$size shmem (never->always)"
> +
> +     # Restore
> +     echo "$saved_shmem_size" > "$SHMEM_SIZE_ENABLED" 2>/dev/null
> +
> +     # Only test one size in detail
> +     break
> +done
> +
> +if [ $found_shmem -eq 0 ]; then
> +     ktap_test_skip "no per-size shmem_enabled files found"
> +fi
> +
> +# Quick smoke test: remaining sizes with shmem_enabled
> +first=1
> +for dir in "$THP_SYSFS"/hugepages-*; do
> +     [ -d "$dir" ] || continue
> +     SHMEM_SIZE_ENABLED="$dir/shmem_enabled"
> +     [ -f "$SHMEM_SIZE_ENABLED" ] || continue
> +
> +     if [ $first -eq 1 ]; then
> +             first=0
> +             continue
> +     fi
> +
> +     size=$(basename "$dir")
> +     saved=$(get_active_mode "$SHMEM_SIZE_ENABLED")
> +
> +     smoke_failed=0
> +     for mode in always inherit within_size advise never; do
> +             echo "$mode" > "$SHMEM_SIZE_ENABLED" 2>/dev/null
> +             active=$(get_active_mode "$SHMEM_SIZE_ENABLED")
> +             if [ "$active" != "$mode" ]; then
> +                     ktap_test_fail "$size shmem: smoke test '$mode' got 
> '$active'"
> +                     smoke_failed=1
> +                     break
> +             fi
> +     done
> +     [ $smoke_failed -eq 0 ] && ktap_test_pass "$size shmem: smoke test all 
> modes"
> +
> +     echo "$saved" > "$SHMEM_SIZE_ENABLED" 2>/dev/null
> +done
> +
> +# --- Test khugepaged tunables ---
> +
> +KHUGEPAGED="$THP_SYSFS/khugepaged"
> +
> +if [ ! -d "$KHUGEPAGED" ]; then
> +     ktap_test_skip "khugepaged directory not found"
> +else
> +     ktap_print_msg "Testing khugepaged tunables ($KHUGEPAGED)"
> +
> +     # Compute HPAGE_PMD_NR for boundary tests
> +     pmd_size=$(cat "$HPAGE_PMD_SIZE_FILE" 2>/dev/null)
> +     page_size=$(getconf PAGE_SIZE)
> +     if [ -n "$pmd_size" ] && [ -n "$page_size" ] && [ "$page_size" -gt 0 ]; 
> then
> +             hpage_pmd_nr=$((pmd_size / page_size))
> +     else
> +             hpage_pmd_nr=512
> +     fi
> +
> +     # Save all tunable values
> +     saved_khp_defrag=$(cat "$KHUGEPAGED/defrag" 2>/dev/null)
> +     saved_khp_max_ptes_none=$(cat "$KHUGEPAGED/max_ptes_none" 2>/dev/null)
> +     saved_khp_max_ptes_swap=$(cat "$KHUGEPAGED/max_ptes_swap" 2>/dev/null)
> +     saved_khp_max_ptes_shared=$(cat "$KHUGEPAGED/max_ptes_shared" 
> 2>/dev/null)
> +     saved_khp_pages_to_scan=$(cat "$KHUGEPAGED/pages_to_scan" 2>/dev/null)
> +     saved_khp_scan_sleep=$(cat "$KHUGEPAGED/scan_sleep_millisecs" 
> 2>/dev/null)
> +     saved_khp_alloc_sleep=$(cat "$KHUGEPAGED/alloc_sleep_millisecs" 
> 2>/dev/null)
> +
> +     # khugepaged/defrag (0/1 flag)
> +     if [ -f "$KHUGEPAGED/defrag" ]; then
> +             test_numeric "$KHUGEPAGED/defrag" "0" "khugepaged/defrag"
> +             test_numeric "$KHUGEPAGED/defrag" "1" "khugepaged/defrag"
> +             test_numeric_invalid "$KHUGEPAGED/defrag" "2" 
> "khugepaged/defrag"
> +             test_numeric_invalid "$KHUGEPAGED/defrag" "bogus" 
> "khugepaged/defrag"
> +     fi
> +
> +     # khugepaged/max_ptes_none (0 .. HPAGE_PMD_NR-1)
> +     if [ -f "$KHUGEPAGED/max_ptes_none" ]; then
> +             test_numeric "$KHUGEPAGED/max_ptes_none" "0" 
> "khugepaged/max_ptes_none"
> +             test_numeric "$KHUGEPAGED/max_ptes_none" "$((hpage_pmd_nr - 
> 1))" \
> +                     "khugepaged/max_ptes_none"
> +             test_numeric_invalid "$KHUGEPAGED/max_ptes_none" 
> "$hpage_pmd_nr" \
> +                     "khugepaged/max_ptes_none (boundary)"
> +     fi
> +
> +     # khugepaged/max_ptes_swap (0 .. HPAGE_PMD_NR-1)
> +     if [ -f "$KHUGEPAGED/max_ptes_swap" ]; then
> +             test_numeric "$KHUGEPAGED/max_ptes_swap" "0" 
> "khugepaged/max_ptes_swap"
> +             test_numeric "$KHUGEPAGED/max_ptes_swap" "$((hpage_pmd_nr - 
> 1))" \
> +                     "khugepaged/max_ptes_swap"
> +             test_numeric_invalid "$KHUGEPAGED/max_ptes_swap" 
> "$hpage_pmd_nr" \
> +                     "khugepaged/max_ptes_swap (boundary)"
> +     fi
> +
> +     # khugepaged/max_ptes_shared (0 .. HPAGE_PMD_NR-1)
> +     if [ -f "$KHUGEPAGED/max_ptes_shared" ]; then
> +             test_numeric "$KHUGEPAGED/max_ptes_shared" "0" 
> "khugepaged/max_ptes_shared"
> +             test_numeric "$KHUGEPAGED/max_ptes_shared" "$((hpage_pmd_nr - 
> 1))" \
> +                     "khugepaged/max_ptes_shared"
> +             test_numeric_invalid "$KHUGEPAGED/max_ptes_shared" 
> "$hpage_pmd_nr" \
> +                     "khugepaged/max_ptes_shared (boundary)"
> +     fi
> +
> +     # khugepaged/pages_to_scan (1 .. UINT_MAX, 0 rejected)
> +     if [ -f "$KHUGEPAGED/pages_to_scan" ]; then
> +             test_numeric "$KHUGEPAGED/pages_to_scan" "1" 
> "khugepaged/pages_to_scan"
> +             test_numeric "$KHUGEPAGED/pages_to_scan" "8" 
> "khugepaged/pages_to_scan"
> +             test_numeric_invalid "$KHUGEPAGED/pages_to_scan" "0" \
> +                     "khugepaged/pages_to_scan (reject 0)"
> +     fi
> +
> +     # khugepaged/scan_sleep_millisecs
> +     if [ -f "$KHUGEPAGED/scan_sleep_millisecs" ]; then
> +             test_numeric "$KHUGEPAGED/scan_sleep_millisecs" "0" \
> +                     "khugepaged/scan_sleep_millisecs"
> +             test_numeric "$KHUGEPAGED/scan_sleep_millisecs" "1000" \
> +                     "khugepaged/scan_sleep_millisecs"
> +     fi
> +
> +     # khugepaged/alloc_sleep_millisecs
> +     if [ -f "$KHUGEPAGED/alloc_sleep_millisecs" ]; then
> +             test_numeric "$KHUGEPAGED/alloc_sleep_millisecs" "0" \
> +                     "khugepaged/alloc_sleep_millisecs"
> +             test_numeric "$KHUGEPAGED/alloc_sleep_millisecs" "1000" \
> +                     "khugepaged/alloc_sleep_millisecs"
> +     fi
> +
> +     # khugepaged/pages_collapsed (read-only)
> +     if [ -f "$KHUGEPAGED/pages_collapsed" ]; then
> +             test_readonly "$KHUGEPAGED/pages_collapsed" 
> "khugepaged/pages_collapsed"
> +     fi
> +
> +     # khugepaged/full_scans (read-only)
> +     if [ -f "$KHUGEPAGED/full_scans" ]; then
> +             test_readonly "$KHUGEPAGED/full_scans" "khugepaged/full_scans"
> +     fi
> +
> +     # Restore all values
> +     [ -n "$saved_khp_defrag" ] && \
> +             echo "$saved_khp_defrag" > "$KHUGEPAGED/defrag" 2>/dev/null
> +     [ -n "$saved_khp_max_ptes_none" ] && \
> +             echo "$saved_khp_max_ptes_none" > "$KHUGEPAGED/max_ptes_none" 
> 2>/dev/null
> +     [ -n "$saved_khp_max_ptes_swap" ] && \
> +             echo "$saved_khp_max_ptes_swap" > "$KHUGEPAGED/max_ptes_swap" 
> 2>/dev/null
> +     [ -n "$saved_khp_max_ptes_shared" ] && \
> +             echo "$saved_khp_max_ptes_shared" > 
> "$KHUGEPAGED/max_ptes_shared" 2>/dev/null
> +     [ -n "$saved_khp_pages_to_scan" ] && \
> +             echo "$saved_khp_pages_to_scan" > "$KHUGEPAGED/pages_to_scan" 
> 2>/dev/null
> +     [ -n "$saved_khp_scan_sleep" ] && \
> +             echo "$saved_khp_scan_sleep" > 
> "$KHUGEPAGED/scan_sleep_millisecs" 2>/dev/null
> +     [ -n "$saved_khp_alloc_sleep" ] && \
> +             echo "$saved_khp_alloc_sleep" > 
> "$KHUGEPAGED/alloc_sleep_millisecs" 2>/dev/null
> +fi
> +
> +# --- Test per-size stats files ---
> +
> +found_stats=0
> +for dir in "$THP_SYSFS"/hugepages-*; do
> +     [ -d "$dir" ] || continue
> +     [ -d "$dir/stats" ] || continue
> +
> +     found_stats=1
> +     size=$(basename "$dir")
> +     ktap_print_msg "Testing per-size stats ($size)"
> +
> +     for stat_file in "$dir"/stats/*; do
> +             [ -f "$stat_file" ] || continue
> +             stat_name=$(basename "$stat_file")
> +             val=$(cat "$stat_file" 2>/dev/null)
> +
> +             if [ -z "$val" ]; then
> +                     ktap_test_fail "$size/stats/$stat_name: read returned 
> empty"
> +                     continue
> +             fi
> +
> +             if echo "$val" | grep -qE '^[0-9]+$'; then
> +                     ktap_test_pass "$size/stats/$stat_name: readable 
> (value=$val)"
> +             else
> +                     ktap_test_fail "$size/stats/$stat_name: expected 
> numeric, got '$val'"
> +             fi
> +     done
> +
> +     # Only test one size
> +     break
> +done
> +
> +if [ $found_stats -eq 0 ]; then
> +     ktap_test_skip "no per-size stats directories found"
> +fi
> +
> +# --- Done ---
> +
> +# The test count is dynamic (depends on available sysfs files and hugepage
> +# sizes), so print the plan at the end.  TAP 13 allows trailing plans.
> +KSFT_NUM_TESTS=$((KTAP_CNT_PASS + KTAP_CNT_FAIL + KTAP_CNT_SKIP))
> +echo "1..$KSFT_NUM_TESTS"
> +ktap_finished
>
> ---
> base-commit: dc420ab7b435928a467b8d03fd3ed80e7d01d720
> change-id: 20260309-thp_selftest_v2-4d32eba70441
>
> Best regards,
> --
> Breno Leitao <[email protected]>
>

Reply via email to