Linux cgroups development
 help / color / mirror / Atom feed
* [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner
@ 2026-07-06 12:06 Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 1/4] cgroup: Add dmem_selftest module Albert Esteve
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Albert Esteve @ 2026-07-06 12:06 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan
  Cc: linux-kernel, cgroups, linux-kselftest, Albert Esteve,
	Eric Chanudet

Hi all,

This small series adds practical test coverage for the dmem
cgroup controller.

The motivation came from following the recent dmem API discussion in
thread [1]. That discussion considered changing the dmem API and
adding a new knob. Currently there are no dedicated tests covering
dmem behaviour, which makes such changes riskier.

Adding selftests has an additional challenge: dmem charging paths
are driver-driven today, so regression testing is harder unless a
suitable driver is present in the test environment.

This series addresses that by adding:
- a kernel-side selftest helper module to trigger charge/uncharge
  from userspace in a controlled way,
- cgroup selftests covering dmem accounting and protection semantics
  (including dmem.max enforcement and byte-granularity checks),
- a virtme-based VM runner for repeatable execution of the dmem tests.

The goal is to make dmem behavior easier to validate when evolving the API
and implementation, while keeping tests deterministic and driver-independent.

Thanks.

[1] - https://lore.kernel.org/all/aZoHfloupKvF2oSu@fedora/

Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
Changes in v5:
- Change parse_first_region() by find_selftest_region to scan
  dmem.capacity lines
- Use exact equality for limit readback, not values_close()
- Write VM tempfile to $SCRIPT_DIR
- Fix KTAP by dropping outer framing in test_dmem
- Use array for optional vng args to handle paths with spaces
- Respect O=/KBUILD_OUTPUT= when building, per bpf/vmtest.sh convention
- Link to v4: https://lore.kernel.org/r/20260519-kunit_cgroups-v4-0-f6c2f498fae4@redhat.com

Changes in v4:
- Fix charged_pool leak in dmem_selftest_init()
- Replace ssh-based VM approach in vmtest-dmem.sh with vng --exec
- Add main() wrapper to vmtest-dmem.sh
- Other small fixes suggested by Sashiko
- Link to v3: https://lore.kernel.org/r/20260504-kunit_cgroups-v3-0-4eac90b76f91@redhat.com

Changes in v3:
- Set charged flag on unexpected over-limit charge success.
- Add CONFIG_DMEM_SELFTEST=m to selftest config.
- Simplify -v to a boolean; single -v was a no-op.
- Unquote kernel_opt to avoid empty-string arg to vng.
- Document -b in usage() output.
- Link to v2: https://lore.kernel.org/r/20260421-kunit_cgroups-v2-0-bb6675d8249c@redhat.com

Changes in v2:
- Fix debugfs_create_dir() error check
- Fix module teardown race: call dmem_selftest_remove() before
  uncharging so debugfs files are torn down
- Use IS_ERR_OR_NULL() in selftest() sanity check
- Add CONFIG_CGROUP_DMEM=y to the cgroup selftest config
- Replace config-file parsing in check_guest_requirements() with
  a direct check of /sys/fs/cgroup/cgroup.controllers
- Add new patch 4 (from Eric Chanudet): vmtest-dmem.sh -b flag
  to configure and build a local kernel tree
- Link to v1: https://lore.kernel.org/r/20260327-kunit_cgroups-v1-0-971b3c739a00@redhat.com

---
Albert Esteve (4):
      cgroup: Add dmem_selftest module
      selftests: cgroup: Add dmem selftest coverage
      selftests: cgroup: Add vmtest-dmem runner script
      selftests: cgroup: handle vmtest-dmem -b to test locally built kernel

 init/Kconfig                                  |  12 +
 kernel/cgroup/Makefile                        |   1 +
 kernel/cgroup/dmem_selftest.c                 | 198 +++++++++++
 tools/testing/selftests/cgroup/.gitignore     |   1 +
 tools/testing/selftests/cgroup/Makefile       |   4 +-
 tools/testing/selftests/cgroup/config         |   2 +
 tools/testing/selftests/cgroup/test_dmem.c    | 486 ++++++++++++++++++++++++++
 tools/testing/selftests/cgroup/vmtest-dmem.sh | 188 ++++++++++
 8 files changed, 891 insertions(+), 1 deletion(-)
---
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
change-id: 20260318-kunit_cgroups-7fb0b9e64017

Best regards,
-- 
Albert Esteve <aesteve@redhat.com>


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

* [PATCH v5 1/4] cgroup: Add dmem_selftest module
  2026-07-06 12:06 [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner Albert Esteve
@ 2026-07-06 12:06 ` Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 2/4] selftests: cgroup: Add dmem selftest coverage Albert Esteve
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Albert Esteve @ 2026-07-06 12:06 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan
  Cc: linux-kernel, cgroups, linux-kselftest, Albert Esteve

Currently, dmem charging is driver-driven through direct
calls to dmem_cgroup_try_charge(), so cgroup selftests
do not have a generic way to trigger charge and uncharge
paths from userspace.

This limits any selftest coverage to configuration/readout
checks unless a specific driver exposing charge hooks is
present in the test environment.

Add kernel/cgroup/dmem_selftest.c as a helper module
(CONFIG_DMEM_SELFTEST) that registers a synthetic dmem region
(dmem_selftest) and exposes debugfs control files:
/sys/kernel/debug/dmem_selftest/charge
/sys/kernel/debug/dmem_selftest/uncharge

Writing a size to charge triggers dmem_cgroup_try_charge() for
the calling task's cgroup (the module calls kstrtou64()).
Writing to uncharge releases the outstanding charge via
dmem_cgroup_uncharge(). Only a single outstanding charge
is supported.

This provides a deterministic, driver-independent mechanism
for exercising dmem accounting paths in selftests.

Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
 init/Kconfig                  |  12 +++
 kernel/cgroup/Makefile        |   1 +
 kernel/cgroup/dmem_selftest.c | 198 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 211 insertions(+)

diff --git a/init/Kconfig b/init/Kconfig
index 5230d4879b1c8..6fab65d6b44ea 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1267,6 +1267,18 @@ config CGROUP_DMEM
 	  As an example, it allows you to restrict VRAM usage for applications
 	  in the DRM subsystem.
 
+config DMEM_SELFTEST
+	tristate "dmem cgroup selftest helper module"
+	depends on CGROUP_DMEM && DEBUG_FS
+	default n
+	help
+	  Builds a small loadable module that registers a dmem region named
+	  "dmem_selftest" and exposes debugfs files under
+	  /sys/kernel/debug/dmem_selftest/ so kselftests can trigger
+	  dmem charge/uncharge operations from userspace.
+
+	  Say N unless you run dmem selftests or develop the dmem controller.
+
 config CGROUP_FREEZER
 	bool "Freezer controller"
 	help
diff --git a/kernel/cgroup/Makefile b/kernel/cgroup/Makefile
index ede31601a363a..febc36e60f9f9 100644
--- a/kernel/cgroup/Makefile
+++ b/kernel/cgroup/Makefile
@@ -8,4 +8,5 @@ obj-$(CONFIG_CPUSETS) += cpuset.o
 obj-$(CONFIG_CPUSETS_V1) += cpuset-v1.o
 obj-$(CONFIG_CGROUP_MISC) += misc.o
 obj-$(CONFIG_CGROUP_DMEM) += dmem.o
+obj-$(CONFIG_DMEM_SELFTEST) += dmem_selftest.o
 obj-$(CONFIG_CGROUP_DEBUG) += debug.o
diff --git a/kernel/cgroup/dmem_selftest.c b/kernel/cgroup/dmem_selftest.c
new file mode 100644
index 0000000000000..625ab2d73923b
--- /dev/null
+++ b/kernel/cgroup/dmem_selftest.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kselftest helper for the dmem cgroup controller.
+ *
+ * Registers a dmem region and debugfs files so tests can trigger charges
+ * from the calling task's cgroup.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cgroup_dmem.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "../../tools/testing/selftests/kselftest_module.h"
+
+#define DM_SELFTEST_REGION_NAME	"dmem_selftest"
+#define DM_SELFTEST_REGION_SIZE	(256ULL * 1024 * 1024)
+
+KSTM_MODULE_GLOBALS();
+
+static struct dmem_cgroup_region *selftest_region;
+static struct dentry *dbg_dir;
+
+static struct dmem_cgroup_pool_state *charged_pool;
+static u64 charged_size;
+static DEFINE_MUTEX(charge_lock);
+
+static ssize_t dmem_selftest_charge_write(struct file *file, const char __user *user_buf,
+					  size_t count, loff_t *ppos)
+{
+	struct dmem_cgroup_pool_state *pool = NULL, *limit = NULL;
+	u64 size;
+	char buf[32];
+	int ret;
+
+	if (!selftest_region)
+		return -ENODEV;
+
+	if (count == 0 || count >= sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, user_buf, count))
+		return -EFAULT;
+	buf[count] = '\0';
+
+	ret = kstrtou64(strim(buf), 0, &size);
+	if (ret)
+		return ret;
+	if (!size || size > DM_SELFTEST_REGION_SIZE)
+		return -EINVAL;
+
+	mutex_lock(&charge_lock);
+	if (charged_pool) {
+		mutex_unlock(&charge_lock);
+		return -EBUSY;
+	}
+
+	ret = dmem_cgroup_try_charge(selftest_region, size, &pool, &limit);
+	if (ret == -EAGAIN && limit)
+		dmem_cgroup_pool_state_put(limit);
+	if (ret) {
+		mutex_unlock(&charge_lock);
+		return ret;
+	}
+
+	charged_pool = pool;
+	charged_size = size;
+	mutex_unlock(&charge_lock);
+
+	return count;
+}
+
+static ssize_t dmem_selftest_uncharge_write(struct file *file, const char __user *user_buf,
+					    size_t count, loff_t *ppos)
+{
+	if (!count)
+		return -EINVAL;
+
+	mutex_lock(&charge_lock);
+	if (!charged_pool) {
+		mutex_unlock(&charge_lock);
+		return -EINVAL;
+	}
+
+	dmem_cgroup_uncharge(charged_pool, charged_size);
+	charged_pool = NULL;
+	charged_size = 0;
+	mutex_unlock(&charge_lock);
+
+	return count;
+}
+
+static const struct file_operations dmem_selftest_charge_fops = {
+	.write = dmem_selftest_charge_write,
+	.llseek = noop_llseek,
+};
+
+static const struct file_operations dmem_selftest_uncharge_fops = {
+	.write = dmem_selftest_uncharge_write,
+	.llseek = noop_llseek,
+};
+
+static int __init dmem_selftest_register(void)
+{
+	int ret = 0;
+
+	selftest_region = dmem_cgroup_register_region(
+		DM_SELFTEST_REGION_SIZE, DM_SELFTEST_REGION_NAME);
+	if (IS_ERR(selftest_region))
+		return PTR_ERR(selftest_region);
+	if (!selftest_region)
+		return -EINVAL;
+
+	dbg_dir = debugfs_create_dir("dmem_selftest", NULL);
+	if (IS_ERR(dbg_dir)) {
+		ret = PTR_ERR(dbg_dir);
+		goto dbgfs_error;
+	}
+
+	debugfs_create_file("charge", 0200, dbg_dir, NULL, &dmem_selftest_charge_fops);
+	debugfs_create_file("uncharge", 0200, dbg_dir, NULL, &dmem_selftest_uncharge_fops);
+
+	pr_info("region '%s' registered; debugfs at dmem_selftest/{charge,uncharge}\n",
+		DM_SELFTEST_REGION_NAME);
+	return ret;
+
+dbgfs_error:
+	dmem_cgroup_unregister_region(selftest_region);
+	dbg_dir = NULL;
+	selftest_region = NULL;
+	return ret;
+}
+
+static void dmem_selftest_remove(void)
+{
+	debugfs_remove_recursive(dbg_dir);
+	dbg_dir = NULL;
+
+	if (selftest_region) {
+		dmem_cgroup_unregister_region(selftest_region);
+		selftest_region = NULL;
+	}
+
+	mutex_lock(&charge_lock);
+	if (charged_pool) {
+		dmem_cgroup_uncharge(charged_pool, charged_size);
+		charged_pool = NULL;
+	}
+	mutex_unlock(&charge_lock);
+}
+
+static void __init selftest(void)
+{
+	KSTM_CHECK_ZERO(!selftest_region);
+	KSTM_CHECK_ZERO(IS_ERR_OR_NULL(dbg_dir));
+}
+
+static int __init dmem_selftest_init(void)
+{
+	int report_rc;
+	int err;
+
+	err = dmem_selftest_register();
+	if (err)
+		return err;
+
+	pr_info("loaded.\n");
+	add_taint(TAINT_TEST, LOCKDEP_STILL_OK);
+	selftest();
+	report_rc = kstm_report(total_tests, failed_tests, skipped_tests);
+	if (report_rc) {
+		dmem_selftest_remove();
+		return report_rc;
+	}
+
+	return 0;
+}
+
+static void __exit dmem_selftest_exit(void)
+{
+	pr_info("unloaded.\n");
+	dmem_selftest_remove();
+}
+
+module_init(dmem_selftest_init);
+module_exit(dmem_selftest_exit);
+
+MODULE_AUTHOR("Albert Esteve <aesteve@redhat.com>");
+MODULE_DESCRIPTION("Kselftest helper for cgroup dmem controller");
+MODULE_LICENSE("GPL");

-- 
2.54.0


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

* [PATCH v5 2/4] selftests: cgroup: Add dmem selftest coverage
  2026-07-06 12:06 [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 1/4] cgroup: Add dmem_selftest module Albert Esteve
@ 2026-07-06 12:06 ` Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 3/4] selftests: cgroup: Add vmtest-dmem runner script Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 4/4] selftests: cgroup: handle vmtest-dmem -b to test locally built kernel Albert Esteve
  3 siblings, 0 replies; 5+ messages in thread
From: Albert Esteve @ 2026-07-06 12:06 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan
  Cc: linux-kernel, cgroups, linux-kselftest, Albert Esteve

Currently, tools/testing/selftests/cgroup/ does not include
a dmem-specific test binary. This leaves dmem charge and
limit behavior largely unvalidated in kselftest coverage.

Add test_dmem and wire it into the cgroup selftests Makefile.
The new test exercises dmem controller behavior through the
dmem_selftest debugfs interface for the dmem_selftest region.

The test adds three complementary checks:
- test_dmem_max creates a nested hierarchy with per-leaf
  dmem.max values and verifies that over-limit charges
  fail while in-limit charges succeed in dmem.current.
- test_dmem_min and test_dmem_low verify that charging
  from a cgroup with the corresponding protection knob
  set updates dmem.current as expected.
- test_dmem_charge_byte_granularity validates accounting
  bounds for non-page-aligned charge sizes and
  uncharge-to-zero behavior.

This provides deterministic userspace coverage for dmem
accounting and hard-limit enforcement using a test helper
module, without requiring subsystem-specific production
drivers.

Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
 tools/testing/selftests/cgroup/.gitignore  |   1 +
 tools/testing/selftests/cgroup/Makefile    |   2 +
 tools/testing/selftests/cgroup/config      |   2 +
 tools/testing/selftests/cgroup/test_dmem.c | 486 +++++++++++++++++++++++++++++
 4 files changed, 491 insertions(+)

diff --git a/tools/testing/selftests/cgroup/.gitignore b/tools/testing/selftests/cgroup/.gitignore
index 952e4448bf070..ea2322598217d 100644
--- a/tools/testing/selftests/cgroup/.gitignore
+++ b/tools/testing/selftests/cgroup/.gitignore
@@ -2,6 +2,7 @@
 test_core
 test_cpu
 test_cpuset
+test_dmem
 test_freezer
 test_hugetlb_memcg
 test_kill
diff --git a/tools/testing/selftests/cgroup/Makefile b/tools/testing/selftests/cgroup/Makefile
index e01584c2189ac..e1a5e9316620e 100644
--- a/tools/testing/selftests/cgroup/Makefile
+++ b/tools/testing/selftests/cgroup/Makefile
@@ -10,6 +10,7 @@ TEST_GEN_FILES := wait_inotify
 TEST_GEN_PROGS  = test_core
 TEST_GEN_PROGS += test_cpu
 TEST_GEN_PROGS += test_cpuset
+TEST_GEN_PROGS += test_dmem
 TEST_GEN_PROGS += test_freezer
 TEST_GEN_PROGS += test_hugetlb_memcg
 TEST_GEN_PROGS += test_kill
@@ -26,6 +27,7 @@ include lib/libcgroup.mk
 $(OUTPUT)/test_core: $(LIBCGROUP_O)
 $(OUTPUT)/test_cpu: $(LIBCGROUP_O)
 $(OUTPUT)/test_cpuset: $(LIBCGROUP_O)
+$(OUTPUT)/test_dmem: $(LIBCGROUP_O)
 $(OUTPUT)/test_freezer: $(LIBCGROUP_O)
 $(OUTPUT)/test_hugetlb_memcg: $(LIBCGROUP_O)
 $(OUTPUT)/test_kill: $(LIBCGROUP_O)
diff --git a/tools/testing/selftests/cgroup/config b/tools/testing/selftests/cgroup/config
index 39f979690dd3b..5728278310a31 100644
--- a/tools/testing/selftests/cgroup/config
+++ b/tools/testing/selftests/cgroup/config
@@ -1,6 +1,8 @@
 CONFIG_CGROUPS=y
 CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_DMEM=y
 CONFIG_CGROUP_FREEZER=y
 CONFIG_CGROUP_SCHED=y
+CONFIG_DMEM_SELFTEST=m
 CONFIG_MEMCG=y
 CONFIG_PAGE_COUNTER=y
diff --git a/tools/testing/selftests/cgroup/test_dmem.c b/tools/testing/selftests/cgroup/test_dmem.c
new file mode 100644
index 0000000000000..6f22ebcd54466
--- /dev/null
+++ b/tools/testing/selftests/cgroup/test_dmem.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the dmem (device memory) cgroup controller.
+ *
+ * Depends on dmem_selftest kernel module.
+ */
+
+#define _GNU_SOURCE
+
+#include <linux/limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "kselftest.h"
+#include "cgroup_util.h"
+
+/* kernel/cgroup/dmem_selftest.c */
+#define DM_SELFTEST_REGION	"dmem_selftest"
+#define DM_SELFTEST_CHARGE	"/sys/kernel/debug/dmem_selftest/charge"
+#define DM_SELFTEST_UNCHARGE	"/sys/kernel/debug/dmem_selftest/uncharge"
+
+/*
+ * Search dmem.capacity for the selftest region by name.
+ *   "<name> <size_in_bytes>"
+ * Returns 1 if the selftest region was found, 0 if absent, -1 on read error.
+ */
+static int find_selftest_region(const char *root, unsigned long long *size_out)
+{
+	char buf[4096];
+	char *line, *saveptr = NULL;
+	char nm[256];
+	unsigned long long sz;
+
+	if (cg_read(root, "dmem.capacity", buf, sizeof(buf)) < 0)
+		return -1;
+
+	for (line = strtok_r(buf, "\n", &saveptr); line;
+	     line = strtok_r(NULL, "\n", &saveptr)) {
+		if (!line[0])
+			continue;
+		if (sscanf(line, "%255s %llu", nm, &sz) < 2)
+			continue;
+		if (strcmp(nm, DM_SELFTEST_REGION) == 0) {
+			*size_out = sz;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Read the numeric limit for @region_name from a multiline
+ * dmem.{min,low,max} file. Returns bytes,
+ * or -1 if the line is "<name> max", or -2 if missing/err.
+ */
+static long long dmem_read_limit_for_region(const char *cgroup, const char *ctrl,
+					    const char *region_name)
+{
+	char buf[4096];
+	char *line, *saveptr = NULL;
+	char fname[256];
+	char fval[64];
+
+	if (cg_read(cgroup, ctrl, buf, sizeof(buf)) < 0)
+		return -2;
+
+	for (line = strtok_r(buf, "\n", &saveptr); line;
+	     line = strtok_r(NULL, "\n", &saveptr)) {
+		if (!line[0])
+			continue;
+		if (sscanf(line, "%255s %63s", fname, fval) != 2)
+			continue;
+		if (strcmp(fname, region_name))
+			continue;
+		if (!strcmp(fval, "max"))
+			return -1;
+		return strtoll(fval, NULL, 0);
+	}
+	return -2;
+}
+
+static long long dmem_read_limit(const char *cgroup, const char *ctrl)
+{
+	return dmem_read_limit_for_region(cgroup, ctrl, DM_SELFTEST_REGION);
+}
+
+static int dmem_write_limit(const char *cgroup, const char *ctrl,
+			    const char *val)
+{
+	char wr[512];
+
+	snprintf(wr, sizeof(wr), "%s %s", DM_SELFTEST_REGION, val);
+	return cg_write(cgroup, ctrl, wr);
+}
+
+static int dmem_selftest_charge_bytes(unsigned long long bytes)
+{
+	char wr[32];
+
+	snprintf(wr, sizeof(wr), "%llu", bytes);
+	return write_text(DM_SELFTEST_CHARGE, wr, strlen(wr));
+}
+
+static int dmem_selftest_uncharge(void)
+{
+	return write_text(DM_SELFTEST_UNCHARGE, "\n", 1);
+}
+
+/*
+ * First, this test creates the following hierarchy:
+ * A
+ * A/B     dmem.max=1M
+ * A/B/C   dmem.max=75K
+ * A/B/D   dmem.max=25K
+ * A/B/E   dmem.max=8K
+ * A/B/F   dmem.max=0
+ *
+ * Then for each leaf cgroup it tries to charge above dmem.max
+ * and expects the charge request to fail and dmem.current to
+ * remain unchanged.
+ *
+ * For leaves with non-zero dmem.max, it additionally charges a
+ * smaller amount and verifies accounting grows within one PAGE_SIZE
+ * rounding bound, then uncharges and verifies dmem.current returns
+ * to the previous value.
+ *
+ */
+static int test_dmem_max(const char *root)
+{
+	static const char * const leaf_max[] = { "75K", "25K", "8K", "0" };
+	static const unsigned long long fail_sz[] = {
+		(75ULL * 1024ULL) + 1ULL,
+		(25ULL * 1024ULL) + 1ULL,
+		(8ULL * 1024ULL) + 1ULL,
+		1ULL
+	};
+	static const unsigned long long pass_sz[] = {
+		4096ULL, 4096ULL, 4096ULL, 0ULL
+	};
+	char *parent[2] = {NULL};
+	char *children[4] = {NULL};
+	unsigned long long cap;
+	long long page_size;
+	long long cur_before, cur_after;
+	int ret = KSFT_FAIL;
+	int charged = 0;
+	int in_child = 0;
+	long long v;
+	int i;
+
+	if (access(DM_SELFTEST_CHARGE, W_OK) != 0)
+		return KSFT_SKIP;
+
+	if (find_selftest_region(root, &cap) != 1)
+		return KSFT_SKIP;
+
+	page_size = sysconf(_SC_PAGESIZE);
+	if (page_size <= 0)
+		goto cleanup;
+
+	parent[0] = cg_name(root, "dmem_prot_0");
+	if (!parent[0])
+		goto cleanup;
+
+	parent[1] = cg_name(parent[0], "dmem_prot_1");
+	if (!parent[1])
+		goto cleanup;
+
+	if (cg_create(parent[0]))
+		goto cleanup;
+
+	if (cg_write(parent[0], "cgroup.subtree_control", "+dmem"))
+		goto cleanup;
+
+	if (cg_create(parent[1]))
+		goto cleanup;
+
+	if (cg_write(parent[1], "cgroup.subtree_control", "+dmem"))
+		goto cleanup;
+
+	for (i = 0; i < 4; i++) {
+		children[i] = cg_name_indexed(parent[1], "dmem_child", i);
+		if (!children[i])
+			goto cleanup;
+		if (cg_create(children[i]))
+			goto cleanup;
+	}
+
+	if (dmem_write_limit(parent[1], "dmem.max", "1M"))
+		goto cleanup;
+	for (i = 0; i < 4; i++)
+		if (dmem_write_limit(children[i], "dmem.max", leaf_max[i]))
+			goto cleanup;
+
+	v = dmem_read_limit(parent[1], "dmem.max");
+	if (v != 1024LL * 1024LL)
+		goto cleanup;
+	v = dmem_read_limit(children[0], "dmem.max");
+	if (v != 75LL * 1024LL)
+		goto cleanup;
+	v = dmem_read_limit(children[1], "dmem.max");
+	if (v != 25LL * 1024LL)
+		goto cleanup;
+	v = dmem_read_limit(children[2], "dmem.max");
+	if (v != 8LL * 1024LL)
+		goto cleanup;
+	v = dmem_read_limit(children[3], "dmem.max");
+	if (v != 0)
+		goto cleanup;
+
+	for (i = 0; i < 4; i++) {
+		if (cg_enter_current(children[i]))
+			goto cleanup;
+		in_child = 1;
+
+		cur_before = dmem_read_limit(children[i], "dmem.current");
+		if (cur_before < 0)
+			goto cleanup;
+
+		if (dmem_selftest_charge_bytes(fail_sz[i]) >= 0) {
+			charged = 1;
+			goto cleanup;
+		}
+
+		cur_after = dmem_read_limit(children[i], "dmem.current");
+		if (cur_after != cur_before)
+			goto cleanup;
+
+		if (pass_sz[i] > 0) {
+			if (dmem_selftest_charge_bytes(pass_sz[i]) < 0)
+				goto cleanup;
+			charged = 1;
+
+			cur_after = dmem_read_limit(children[i], "dmem.current");
+			if (cur_after < cur_before + (long long)pass_sz[i])
+				goto cleanup;
+			if (cur_after > cur_before + (long long)pass_sz[i] + page_size)
+				goto cleanup;
+
+			if (dmem_selftest_uncharge() < 0)
+				goto cleanup;
+			charged = 0;
+
+			cur_after = dmem_read_limit(children[i], "dmem.current");
+			if (cur_after != cur_before)
+				goto cleanup;
+		}
+
+		if (cg_enter_current(root))
+			goto cleanup;
+		in_child = 0;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (charged)
+		dmem_selftest_uncharge();
+	if (in_child)
+		cg_enter_current(root);
+	for (i = 3; i >= 0; i--) {
+		if (!children[i])
+			continue;
+		cg_destroy(children[i]);
+		free(children[i]);
+	}
+	for (i = 1; i >= 0; i--) {
+		if (!parent[i])
+			continue;
+		cg_destroy(parent[i]);
+		free(parent[i]);
+	}
+	return ret;
+}
+
+/*
+ * This test sets dmem.min and dmem.low on a child cgroup, then charge
+ * from that context and verify dmem.current tracks the charged bytes
+ * (within one page rounding).
+ */
+static int test_dmem_charge_with_attr(const char *root, bool min)
+{
+	unsigned long long cap;
+	const unsigned long long charge_sz = 12345ULL;
+	const char *attribute = min ? "dmem.min" : "dmem.low";
+	int ret = KSFT_FAIL;
+	char *cg = NULL;
+	long long cur;
+	long long page_size;
+	int charged = 0;
+	int in_child = 0;
+
+	if (access(DM_SELFTEST_CHARGE, W_OK) != 0)
+		return KSFT_SKIP;
+
+	if (find_selftest_region(root, &cap) != 1)
+		return KSFT_SKIP;
+
+	page_size = sysconf(_SC_PAGESIZE);
+	if (page_size <= 0)
+		goto cleanup;
+
+	cg = cg_name(root, "test_dmem_attr");
+	if (!cg)
+		goto cleanup;
+
+	if (cg_create(cg))
+		goto cleanup;
+
+	if (cg_enter_current(cg))
+		goto cleanup;
+	in_child = 1;
+
+	if (dmem_write_limit(cg, attribute, "16K"))
+		goto cleanup;
+
+	if (dmem_selftest_charge_bytes(charge_sz) < 0)
+		goto cleanup;
+	charged = 1;
+
+	cur = dmem_read_limit(cg, "dmem.current");
+	if (cur < (long long)charge_sz)
+		goto cleanup;
+	if (cur > (long long)charge_sz + page_size)
+		goto cleanup;
+
+	if (dmem_selftest_uncharge() < 0)
+		goto cleanup;
+	charged = 0;
+
+	cur = dmem_read_limit(cg, "dmem.current");
+	if (cur != 0)
+		goto cleanup;
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (charged)
+		dmem_selftest_uncharge();
+	if (in_child)
+		cg_enter_current(root);
+	cg_destroy(cg);
+	free(cg);
+	return ret;
+}
+
+static int test_dmem_min(const char *root)
+{
+	return test_dmem_charge_with_attr(root, true);
+}
+
+static int test_dmem_low(const char *root)
+{
+	return test_dmem_charge_with_attr(root, false);
+}
+
+/*
+ * This test charges non-page-aligned byte sizes and verify dmem.current
+ * stays consistent: it must account at least the requested bytes and
+ * never exceed one kernel page of rounding overhead. Then uncharge must
+ * return usage to 0.
+ */
+static int test_dmem_charge_byte_granularity(const char *root)
+{
+	static const unsigned long long sizes[] = { 1ULL, 4095ULL, 4097ULL, 12345ULL };
+	char *cg = NULL;
+	unsigned long long cap;
+	long long cur;
+	long long page_size;
+	int ret = KSFT_FAIL;
+	int charged = 0;
+	int in_child = 0;
+	size_t i;
+
+	if (access(DM_SELFTEST_CHARGE, W_OK) != 0)
+		return KSFT_SKIP;
+
+	if (find_selftest_region(root, &cap) != 1)
+		return KSFT_SKIP;
+
+	page_size = sysconf(_SC_PAGESIZE);
+	if (page_size <= 0)
+		goto cleanup;
+
+	cg = cg_name(root, "dmem_dbg_byte_gran");
+	if (!cg)
+		goto cleanup;
+
+	if (cg_create(cg))
+		goto cleanup;
+
+	if (dmem_write_limit(cg, "dmem.max", "8M"))
+		goto cleanup;
+
+	if (cg_enter_current(cg))
+		goto cleanup;
+	in_child = 1;
+
+	for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+		if (dmem_selftest_charge_bytes(sizes[i]) < 0)
+			goto cleanup;
+		charged = 1;
+
+		cur = dmem_read_limit(cg, "dmem.current");
+		if (cur < (long long)sizes[i])
+			goto cleanup;
+		if (cur > (long long)sizes[i] + page_size)
+			goto cleanup;
+
+		if (dmem_selftest_uncharge() < 0)
+			goto cleanup;
+		charged = 0;
+
+		cur = dmem_read_limit(cg, "dmem.current");
+		if (cur != 0)
+			goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (charged)
+		dmem_selftest_uncharge();
+	if (in_child)
+		cg_enter_current(root);
+	if (cg) {
+		cg_destroy(cg);
+		free(cg);
+	}
+	return ret;
+}
+
+#define T(x) { x, #x }
+struct dmem_test {
+	int (*fn)(const char *root);
+	const char *name;
+} tests[] = {
+	T(test_dmem_max),
+	T(test_dmem_min),
+	T(test_dmem_low),
+	T(test_dmem_charge_byte_granularity),
+};
+#undef T
+
+int main(int argc, char **argv)
+{
+	char root[PATH_MAX];
+	int i;
+
+	ksft_print_header();
+	ksft_set_plan(ARRAY_SIZE(tests));
+
+	if (cg_find_unified_root(root, sizeof(root), NULL))
+		ksft_exit_skip("cgroup v2 isn't mounted\n");
+
+	if (cg_read_strstr(root, "cgroup.controllers", "dmem"))
+		ksft_exit_skip("dmem controller isn't available (CONFIG_CGROUP_DMEM?)\n");
+
+	if (cg_read_strstr(root, "cgroup.subtree_control", "dmem"))
+		if (cg_write(root, "cgroup.subtree_control", "+dmem"))
+			ksft_exit_skip("Failed to enable dmem controller\n");
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		switch (tests[i].fn(root)) {
+		case KSFT_PASS:
+			ksft_test_result_pass("%s\n", tests[i].name);
+			break;
+		case KSFT_SKIP:
+			ksft_test_result_skip(
+				"%s (need CONFIG_DMEM_SELFTEST, modprobe dmem_selftest)\n",
+				tests[i].name);
+			break;
+		default:
+			ksft_test_result_fail("%s\n", tests[i].name);
+			break;
+		}
+	}
+
+	ksft_finished();
+}

-- 
2.54.0


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

* [PATCH v5 3/4] selftests: cgroup: Add vmtest-dmem runner script
  2026-07-06 12:06 [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 1/4] cgroup: Add dmem_selftest module Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 2/4] selftests: cgroup: Add dmem selftest coverage Albert Esteve
@ 2026-07-06 12:06 ` Albert Esteve
  2026-07-06 12:06 ` [PATCH v5 4/4] selftests: cgroup: handle vmtest-dmem -b to test locally built kernel Albert Esteve
  3 siblings, 0 replies; 5+ messages in thread
From: Albert Esteve @ 2026-07-06 12:06 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan
  Cc: linux-kernel, cgroups, linux-kselftest, Albert Esteve

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.

vmtest-dmem.sh is placed in TEST_FILES rather than TEST_PROGS so
it is installed alongside the test suite but not invoked
automatically by run_kselftest.sh. It requires a VM-capable host
and is intended to be run manually.

Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
 tools/testing/selftests/cgroup/Makefile       |   2 +-
 tools/testing/selftests/cgroup/vmtest-dmem.sh | 149 ++++++++++++++++++++++++++
 2 files changed, 150 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..0bb6529112b54
--- /dev/null
+++ b/tools/testing/selftests/cgroup/vmtest-dmem.sh
@@ -0,0 +1,149 @@
+#!/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
+
+	# Write the VM-side script into the script directory so it is
+	# accessible in the guest via the --rw host filesystem mount.
+	VM_SCRIPT="$(mktemp --suffix=.sh "${SCRIPT_DIR}/.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..."
+	run_vm --exec "bash ${VM_SCRIPT}"
+}
+
+main "$@"

-- 
2.54.0


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

* [PATCH v5 4/4] selftests: cgroup: handle vmtest-dmem -b to test locally built kernel
  2026-07-06 12:06 [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner Albert Esteve
                   ` (2 preceding siblings ...)
  2026-07-06 12:06 ` [PATCH v5 3/4] selftests: cgroup: Add vmtest-dmem runner script Albert Esteve
@ 2026-07-06 12:06 ` Albert Esteve
  3 siblings, 0 replies; 5+ messages in thread
From: Albert Esteve @ 2026-07-06 12:06 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan
  Cc: linux-kernel, cgroups, linux-kselftest, Albert Esteve,
	Eric Chanudet

Currently vmtest-dmem.sh relies on the host's running kernel or a
pre-built one when booting the virtme-ng VM, with no option to
configure and build a local kernel tree directly.

This adds friction to the development cycle: the user must manually
run vng --kconfig with the correct config fragment, build the kernel,
and pass the result to the script.

Add a -b flag that automates this workflow.  When set, handle_build()
configures the kernel using vng --kconfig with the selftest config
fragment, builds it with make -j$(nproc), and run_vm() passes the
local tree to vng --run so the VM boots the freshly built kernel.

Signed-off-by: Eric Chanudet <echanude@redhat.com>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
 tools/testing/selftests/cgroup/vmtest-dmem.sh | 47 ++++++++++++++++++++++++---
 1 file changed, 43 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/cgroup/vmtest-dmem.sh b/tools/testing/selftests/cgroup/vmtest-dmem.sh
index 0bb6529112b54..4d0e2c0511e5b 100755
--- a/tools/testing/selftests/cgroup/vmtest-dmem.sh
+++ b/tools/testing/selftests/cgroup/vmtest-dmem.sh
@@ -15,6 +15,7 @@ readonly KERNEL_CHECKOUT="$(realpath "${SCRIPT_DIR}"/../../../../)"
 
 source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
 
+BUILD=0
 QEMU="qemu-system-$(uname -m)"
 VERBOSE=0
 SHELL_MODE=0
@@ -26,10 +27,22 @@ function usage() {
 	cat <<EOF
 $0 [OPTIONS]
 Options:
+	-b	Build kernel from source tree before booting
 	-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
+
+If you build your kernel using KBUILD_OUTPUT= or O= options, these
+can be passed as environment variables to the script:
+
+  O=<build_path> $0 -b
+
+or
+
+  KBUILD_OUTPUT=<build_path> $0 -b
+
+O= takes precedence over KBUILD_OUTPUT= if both are set.
 EOF
 }
 
@@ -60,17 +73,41 @@ function check_deps() {
 	done
 }
 
+function handle_build() {
+	[[ "${BUILD}" -eq 1 ]] || return 0
+
+	[[ -f "${KERNEL_CHECKOUT}/kernel/cgroup/dmem_selftest.c" ]] || \
+		fail "-b requires vmtest-dmem.sh called from the kernel source tree"
+
+	# Figure out where the kernel is being built.
+	# O takes precedence over KBUILD_OUTPUT.
+	local out_args=()
+	if [[ -n "${O:-}" ]]; then
+		out_args=(O="${O}")
+	elif [[ -n "${KBUILD_OUTPUT:-}" ]]; then
+		out_args=(KBUILD_OUTPUT="${KBUILD_OUTPUT}")
+	fi
+
+	pushd "${KERNEL_CHECKOUT}" &>/dev/null
+	vng --kconfig --config "${SCRIPT_DIR}"/config "${out_args[@]}" || \
+		fail "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
+	make "${out_args[@]}" -j"$(nproc 2>/dev/null || echo 1)" || \
+		fail "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
+	popd &>/dev/null
+}
+
 # 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=""
+	local vng_args=()
 
-	[[ "${VERBOSE}" -eq 1 ]] && verbose_opt="--verbose"
+	[[ "${BUILD}" -eq 1 ]] && vng_args+=("${KERNEL_CHECKOUT}")
+	[[ "${VERBOSE}" -eq 1 ]] && vng_args+=("--verbose")
 
 	vng \
 		--run \
-		${verbose_opt:+"${verbose_opt}"} \
+		"${vng_args[@]}" \
 		--qemu="$(command -v "${QEMU}")" \
 		--user root \
 		--rw \
@@ -78,10 +115,11 @@ function run_vm() {
 }
 
 function main() {
-	while getopts ':hvq:s' opt; do
+	while getopts ':hvq:sb' opt; do
 		case "${opt}" in
 		v) VERBOSE=1 ;;
 		q) QEMU="${OPTARG}" ;;
+		b) BUILD=1 ;;
 		s) SHELL_MODE=1 ;;
 		h) usage; exit 0 ;;
 		*) usage; exit 1 ;;
@@ -89,6 +127,7 @@ function main() {
 	done
 
 	check_deps
+	handle_build
 
 	if [[ "${SHELL_MODE}" -eq 1 ]]; then
 		echo "Starting interactive shell in VM. Exit to stop VM."

-- 
2.54.0


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

end of thread, other threads:[~2026-07-06 12:09 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 12:06 [PATCH v5 0/4] cgroup: dmem: add selftest helper, coverage, and VM runner Albert Esteve
2026-07-06 12:06 ` [PATCH v5 1/4] cgroup: Add dmem_selftest module Albert Esteve
2026-07-06 12:06 ` [PATCH v5 2/4] selftests: cgroup: Add dmem selftest coverage Albert Esteve
2026-07-06 12:06 ` [PATCH v5 3/4] selftests: cgroup: Add vmtest-dmem runner script Albert Esteve
2026-07-06 12:06 ` [PATCH v5 4/4] selftests: cgroup: handle vmtest-dmem -b to test locally built kernel Albert Esteve

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox