[PATCH v4 3/4] selftests: cgroup: Add vmtest-dmem runner script

From: Albert Esteve

Date: Tue May 19 2026 - 07:12:02 EST


Currently, test_dmem relies on the dmem_selftest helper module
and a VM setup that may not have the helper preinstalled.
This makes automated coverage of dmem charge paths harder in
virtme-based runs.

Add tools/testing/selftests/cgroup/vmtest-dmem.sh to provide a
repeatable VM workflow for dmem tests. The script uses vng --exec
to run the test directly inside a virtme-ng guest with minimal
setup.

The script boots a virtme-ng guest, validates dmem controller
availability, ensures the dmem helper path is present, and runs
tools/testing/selftests/cgroup/test_dmem. If the helper is not
available as a loaded module, it attempts module build/load for
the running guest kernel before executing the test binary.

The runner also supports interactive shell mode (-s) and reuses
the verbosity and KTAP exit-code conventions used by other vmtest
scripts, so it integrates with existing kselftest workflows.

Signed-off-by: Albert Esteve <aesteve@xxxxxxxxxx>
---
tools/testing/selftests/cgroup/Makefile | 2 +-
tools/testing/selftests/cgroup/vmtest-dmem.sh | 156 ++++++++++++++++++++++++++
2 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/cgroup/Makefile b/tools/testing/selftests/cgroup/Makefile
index e1a5e9316620e..2c407710c6e3b 100644
--- a/tools/testing/selftests/cgroup/Makefile
+++ b/tools/testing/selftests/cgroup/Makefile
@@ -3,7 +3,7 @@ CFLAGS += -Wall -pthread

all: ${HELPER_PROGS}

-TEST_FILES := with_stress.sh
+TEST_FILES := with_stress.sh vmtest-dmem.sh
TEST_PROGS := test_stress.sh test_cpuset_prs.sh test_cpuset_v1_hp.sh
TEST_GEN_FILES := wait_inotify
# Keep the lists lexicographically sorted
diff --git a/tools/testing/selftests/cgroup/vmtest-dmem.sh b/tools/testing/selftests/cgroup/vmtest-dmem.sh
new file mode 100755
index 0000000000000..b395b7153f635
--- /dev/null
+++ b/tools/testing/selftests/cgroup/vmtest-dmem.sh
@@ -0,0 +1,156 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2026 Red Hat, Inc.
+#
+# Run cgroup test_dmem inside a virtme-ng VM.
+# Dependencies:
+# * virtme-ng
+# * qemu (used by virtme-ng)
+
+set -euo pipefail
+
+readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
+readonly KERNEL_CHECKOUT="$(realpath "${SCRIPT_DIR}"/../../../../)"
+
+source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
+
+QEMU="qemu-system-$(uname -m)"
+VERBOSE=0
+SHELL_MODE=0
+GUEST_TREE="${GUEST_TREE:-$KERNEL_CHECKOUT}"
+
+VM_SCRIPT=""
+
+function usage() {
+ cat <<EOF
+$0 [OPTIONS]
+Options:
+ -q QEMU binary/path (default: ${QEMU})
+ -s Start interactive shell in VM instead of running tests
+ -v Verbose output (vng boot logs on stdout)
+ -h Display this help
+EOF
+}
+
+function cleanup() {
+ rm -f "${VM_SCRIPT}"
+}
+trap cleanup EXIT
+
+function skip() {
+ local msg=${1:-""}
+
+ ktap_test_skip "${msg}"
+ exit "${KSFT_SKIP}"
+}
+
+function fail() {
+ local msg=${1:-""}
+
+ ktap_test_fail "${msg}"
+ exit "${KSFT_FAIL}"
+}
+
+function check_deps() {
+ for dep in vng "${QEMU}"; do
+ if ! command -v "${dep}" >/dev/null 2>&1; then
+ skip "dependency ${dep} not found"
+ fi
+ done
+}
+
+# Run vng with common flags. Extra arguments are appended by the caller:
+# --exec <script> for automated test runs
+# (nothing) for interactive shell mode
+function run_vm() {
+ local verbose_opt=""
+
+ [[ "${VERBOSE}" -eq 1 ]] && verbose_opt="--verbose"
+
+ vng \
+ --run \
+ ${verbose_opt:+"${verbose_opt}"} \
+ --qemu="$(command -v "${QEMU}")" \
+ --user root \
+ --rw \
+ "$@"
+}
+
+function main() {
+ while getopts ':hvq:s' opt; do
+ case "${opt}" in
+ v) VERBOSE=1 ;;
+ q) QEMU="${OPTARG}" ;;
+ s) SHELL_MODE=1 ;;
+ h) usage; exit 0 ;;
+ *) usage; exit 1 ;;
+ esac
+ done
+
+ check_deps
+
+ if [[ "${SHELL_MODE}" -eq 1 ]]; then
+ echo "Starting interactive shell in VM. Exit to stop VM."
+ run_vm
+ exit 0
+ fi
+
+ ktap_print_header
+ ktap_set_plan 1
+
+ # Write the VM-side script to a tempfile. Because vng mounts the host
+ # filesystem read-write via --rw, the guest can read it at the same path.
+ VM_SCRIPT="$(mktemp --suffix=.sh /tmp/dmem_vmtest_XXXX)"
+
+ cat > "${VM_SCRIPT}" << EOF
+#!/bin/bash
+set -euo pipefail
+
+mountpoint -q /sys/kernel/debug || mount -t debugfs none /sys/kernel/debug
+
+# Verify cgroup controllers are available.
+if ! grep -q dmem /sys/fs/cgroup/cgroup.controllers || \
+ ! grep -q memory /sys/fs/cgroup/cgroup.controllers; then
+ echo "guest kernel missing CONFIG_CGROUP_DMEM or CONFIG_MEMCG" >&2
+ exit 1
+fi
+
+# Load dmem_selftest: try built-in, then modprobe, then build + insmod.
+if [[ -e /sys/kernel/debug/dmem_selftest/charge ]]; then
+ echo "dmem_selftest ready (built-in or already loaded)"
+elif modprobe -q dmem_selftest 2>/dev/null && \
+ [[ -e /sys/kernel/debug/dmem_selftest/charge ]]; then
+ echo "dmem_selftest ready (modprobe)"
+else
+ kdir="/lib/modules/\$(uname -r)/build"
+ if [[ -d "\$kdir" ]]; then
+ echo "Building dmem_selftest.ko against running guest kernel..."
+ if make -C "\$kdir" M="${GUEST_TREE}/kernel/cgroup" \
+ CONFIG_DMEM_SELFTEST=m modules; then
+ insmod "${GUEST_TREE}/kernel/cgroup/dmem_selftest.ko" \
+ 2>/dev/null || modprobe -q dmem_selftest 2>/dev/null || true
+ fi
+ fi
+ if [[ ! -e /sys/kernel/debug/dmem_selftest/charge ]]; then
+ echo "dmem_selftest unavailable (modprobe/build+insmod failed)" >&2
+ exit 1
+ fi
+ echo "dmem_selftest ready (built + insmod)"
+fi
+
+echo "Running cgroup/test_dmem in VM..."
+cd "${GUEST_TREE}"
+make -C tools/testing/selftests TARGETS=cgroup
+./tools/testing/selftests/cgroup/test_dmem
+EOF
+
+ echo "Booting virtme-ng VM..."
+ if run_vm --exec "bash ${VM_SCRIPT}"; then
+ ktap_test_pass "test_dmem"
+ else
+ fail "test_dmem"
+ fi
+}
+
+main "$@"

--
2.53.0