mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] lib/tests: add KUnit tests for the priority sorted list (plist)
@ 2026-11-03 11:36 Yuehong Wu
  2026-11-05 14:30 ` kernel test robot
  0 siblings, 1 reply; 2+ messages in thread
From: Yuehong Wu @ 2026-11-03 11:36 UTC (permalink / raw)
  To: linux-kernel; +Cc: akpm, richard120310, handyhandyman.adam, tglx

lib/plist.c has no KUnit coverage; its only test is the in-module
plist_test() under CONFIG_DEBUG_PLIST, which BUG_ON()s on the first
violation and does not report results in KTAP format.  The code has seen
three regression-class changes since mid-2024:

  commit c8dab79f9eef ("lib/plist.c: avoid worst case scenario in plist_add")
  commit 95d4b3450ebe ("lib/plist.c: add shortcut for plist_requeue()")
  commit 43c91b152243 ("lib/plist: fix plist_requeue() corrupting order in the last bucket")

The last one has no regression test: plist_kunit_requeue_last_bucket()
covers exactly that wrap-around, and the other cases pin down the ordering
invariants the previous two changes rely on.  KUnit coverage makes such
ordering regressions visible in CI, where CONFIG_DEBUG_PLIST is not
normally enabled.

Add lib/tests/plist_kunit.c, a KUnit suite that exercises plist_add(),
plist_del() and plist_requeue() and validates the invariants documented
in include/linux/plist.h after every operation:

  - node_list stays sorted by ascending numeric priority (descending
    priority order) with FIFO order inside a priority bucket;

  - exactly the first node of each bucket is linked on prio_list, and
    the headless prio_list ring visits the bucket heads in node_list
    order before wrapping back to the first bucket (a single bucket
    degenerates to a self loop on the first node).

The fixed cases cover bucket head handover on delete (first, middle and
last bucket), the plist_requeue() shortcut with and without a following
bucket, requeueing the head of the first and of the last bucket where the
ring wraps around, the INT_MIN/INT_MAX extremes, and plist_first_entry()/
plist_last_entry().  A deterministic pseudo-random add/del/requeue churn
test mirrors the in-module test but draws priorities from a small range
so that multi-node buckets actually occur, and every requeue in the loop
moves a node (a requeue without a same-priority successor is a no-op).

The suite is wired up as CONFIG_PLIST_KUNIT_TEST next to the other
library KUnit tests and is enabled by default with KUNIT_ALL_TESTS.

The tests were built into an x86_64 kernel and run under QEMU/KVM with
kunit.enable=1 kunit.filter_glob=plist*: 17 of 17 tests pass.

Signed-off-by: Yuehong Wu <wuyuehong@cmss.chinamobile.com>
---
 lib/Kconfig.debug       |  19 ++
 lib/tests/Makefile      |   1 +
 lib/tests/plist_kunit.c | 650 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 670 insertions(+)
 create mode 100644 lib/tests/plist_kunit.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638..cbf7ab2 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3065,6 +3065,25 @@ config MIN_HEAP_KUNIT_TEST
 
 	  If unsure, say N
 
+config PLIST_KUNIT_TEST
+	tristate "KUnit test for priority sorted list (plist)" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  This builds the priority sorted list (plist) KUnit test suite.
+	  It checks that plist_add(), plist_del() and plist_requeue()
+	  maintain the ordering invariants of the two-level list, including
+	  the priority bucket boundary cases.
+
+	  KUnit tests run during boot and print their results in KTAP format.
+	  Only useful for kernel developers running the KUnit test harness,
+	  not for production builds.
+
+	  For more information on KUnit and unit tests in general please refer
+	  to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+	  If unsure, say N.
+
 config IS_SIGNED_TYPE_KUNIT_TEST
 	tristate "Test is_signed_type() macro" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index df0537e..84569a1 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
 obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o
 CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare)
 obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o
+obj-$(CONFIG_PLIST_KUNIT_TEST) += plist_kunit.o
 obj-$(CONFIG_PRANDOM_KUNIT_TEST) += random32_kunit.o
 obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o
 obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o
diff --git a/lib/tests/plist_kunit.c b/lib/tests/plist_kunit.c
new file mode 100644
index 0000000..c108363
--- /dev/null
+++ b/lib/tests/plist_kunit.c
@@ -0,0 +1,650 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for the priority sorted list (plist) structures.
+ *
+ * Checks that plist_add(), plist_del() and plist_requeue() maintain the
+ * ordering invariants documented in include/linux/plist.h, with extra
+ * attention to the priority bucket boundary cases of the prio_list ring.
+ */
+
+#include <kunit/test.h>
+
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/plist.h>
+
+/* Upper bound for the node_list walk, so a corrupted list fails instead
+ * of hanging the boot.
+ */
+#define PLIST_KUNIT_MAX_NODES	4096
+
+struct plist_kunit_item {
+	int			id;
+	struct plist_node	node;
+};
+
+/*
+ * Walk both the node_list and the prio_list ring and check the invariants
+ * of a plist:
+ *
+ *  - node_list is sorted by ascending numeric priority (i.e. descending
+ *    priority order), with same-priority nodes in FIFO order;
+ *  - exactly the first node of every priority bucket is linked on the
+ *    prio_list ring, and the headless ring visits the bucket heads in
+ *    node_list order before wrapping back to the first bucket;
+ *  - when the list holds a single bucket the ring degenerates to a self
+ *    loop on the first node, which list_empty() reports as empty.
+ *
+ * Returns the number of nodes on the list.
+ */
+static int plist_kunit_verify(struct kunit *test, struct plist_head *head)
+{
+	struct plist_node *pos, *prev = NULL, *first, *ring, *next;
+	int nr_nodes = 0, nr_heads = 0, nr_ring = 0;
+
+	if (plist_head_empty(head)) {
+		/* Same predicate on the other half of the head node. */
+		KUNIT_EXPECT_PTR_EQ(test, head->node_list.prev,
+				    &head->node_list);
+		return 0;
+	}
+
+	first = plist_first(head);
+
+	plist_for_each(pos, head) {
+		if (++nr_nodes > PLIST_KUNIT_MAX_NODES) {
+			KUNIT_FAIL(test, "node_list does not terminate");
+			return nr_nodes;
+		}
+
+		if (prev) {
+			KUNIT_EXPECT_LE(test, prev->prio, pos->prio);
+			/*
+			 * A node sharing its predecessor's priority is a
+			 * bucket follower and must not be linked on the
+			 * prio_list ring; a node starting a new bucket must.
+			 */
+			if (prev->prio == pos->prio) {
+				KUNIT_EXPECT_TRUE(test,
+						  list_empty(&pos->prio_list));
+			} else {
+				KUNIT_EXPECT_FALSE(test,
+						   list_empty(&pos->prio_list));
+				nr_heads++;
+			}
+		} else {
+			nr_heads++;
+		}
+		prev = pos;
+	}
+
+	KUNIT_EXPECT_PTR_EQ(test, prev, plist_last(head));
+
+	if (nr_heads == 1) {
+		/* Single bucket: the ring is a self loop on the first node. */
+		KUNIT_EXPECT_TRUE(test, list_empty(&first->prio_list));
+		return nr_nodes;
+	}
+
+	KUNIT_EXPECT_FALSE(test, list_empty(&first->prio_list));
+
+	ring = first;
+	do {
+		if (++nr_ring > nr_heads) {
+			KUNIT_FAIL(test, "prio_list ring does not close");
+			return nr_nodes;
+		}
+		KUNIT_EXPECT_FALSE(test, list_empty(&ring->prio_list));
+		next = list_entry(ring->prio_list.next, struct plist_node,
+				  prio_list);
+		if (next != first)
+			KUNIT_EXPECT_LT(test, ring->prio, next->prio);
+		ring = next;
+	} while (ring != first);
+
+	KUNIT_EXPECT_EQ(test, nr_ring, nr_heads);
+
+	return nr_nodes;
+}
+
+static void plist_kunit_add_empty(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a;
+
+	plist_node_init(&a, 10);
+
+	KUNIT_EXPECT_TRUE(test, plist_head_empty(&head));
+	KUNIT_EXPECT_TRUE(test, plist_node_empty(&a));
+
+	plist_add(&a, &head);
+
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 1);
+	KUNIT_EXPECT_FALSE(test, plist_head_empty(&head));
+	KUNIT_EXPECT_FALSE(test, plist_node_empty(&a));
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &a);
+}
+
+static void plist_kunit_add_order(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node n[4];
+	int i;
+
+	plist_node_init(&n[0], 30);
+	plist_node_init(&n[1], 10);
+	plist_node_init(&n[2], 40);
+	plist_node_init(&n[3], 20);
+
+	/* Insert the nodes out of order. */
+	for (i = 0; i < ARRAY_SIZE(n); i++)
+		plist_add(&n[i], &head);
+
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 4);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &n[1]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&n[1]), &n[3]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&n[3]), &n[0]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&n[0]), &n[2]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &n[2]);
+}
+
+static void plist_kunit_add_duplicates(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c, d, e;
+
+	plist_node_init(&a, 5);
+	plist_node_init(&b, 5);
+	plist_node_init(&c, 9);
+	plist_node_init(&d, 5);
+	plist_node_init(&e, 7);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+	plist_add(&d, &head);
+	plist_add(&e, &head);
+
+	/* node_list: a(5), b(5), d(5), e(7), c(9) -- FIFO in a bucket. */
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 5);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &b);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &d);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&d), &e);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &c);
+
+	/* Only the first node of each bucket leads the prio_list ring. */
+	KUNIT_EXPECT_FALSE(test, list_empty(&a.prio_list));
+	KUNIT_EXPECT_TRUE(test, list_empty(&b.prio_list));
+	KUNIT_EXPECT_TRUE(test, list_empty(&d.prio_list));
+	KUNIT_EXPECT_FALSE(test, list_empty(&e.prio_list));
+	KUNIT_EXPECT_FALSE(test, list_empty(&c.prio_list));
+
+	KUNIT_EXPECT_PTR_EQ(test, a.prio_list.next, &e.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, e.prio_list.next, &c.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, c.prio_list.next, &a.prio_list);
+}
+
+/*
+ * Adding ascending priorities makes every node the new last bucket, the
+ * case the reverse walk in plist_add() is meant to short-circuit.
+ */
+static void plist_kunit_add_ascending(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node n[16];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(n); i++) {
+		plist_node_init(&n[i], i);
+		plist_add(&n[i], &head);
+		KUNIT_ASSERT_EQ(test, plist_kunit_verify(test, &head), i + 1);
+	}
+
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &n[0]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &n[15]);
+}
+
+/* Adding descending priorities makes every node the new first bucket. */
+static void plist_kunit_add_descending(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node n[16];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(n); i++) {
+		plist_node_init(&n[i], ARRAY_SIZE(n) - i);
+		plist_add(&n[i], &head);
+		KUNIT_ASSERT_EQ(test, plist_kunit_verify(test, &head), i + 1);
+	}
+
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &n[15]);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &n[0]);
+}
+
+static void plist_kunit_add_extremes(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 0);
+	plist_node_init(&b, INT_MAX);
+	plist_node_init(&c, INT_MIN);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	/* INT_MIN is the highest priority, INT_MAX the lowest. */
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &c);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&c), &a);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &b);
+}
+
+static void plist_kunit_del_bucket_head(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 1);
+	plist_node_init(&c, 2);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	/* Removing the bucket head hands prio_list over to the successor. */
+	plist_del(&a, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_TRUE(test, plist_node_empty(&a));
+	KUNIT_EXPECT_FALSE(test, list_empty(&b.prio_list));
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &b);
+
+	/* Removing the last node of the last bucket empties the list. */
+	plist_del(&c, &head);
+	plist_del(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 0);
+	KUNIT_EXPECT_TRUE(test, plist_head_empty(&head));
+
+	/* A removed node can be added back. */
+	plist_add(&a, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 1);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+}
+
+static void plist_kunit_del_bucket_follower(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 1);
+	plist_node_init(&c, 2);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	/* Removing a bucket follower must not disturb the prio_list ring. */
+	plist_del(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_FALSE(test, list_empty(&a.prio_list));
+	KUNIT_EXPECT_FALSE(test, list_empty(&c.prio_list));
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_PTR_EQ(test, a.prio_list.next, &c.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, c.prio_list.next, &a.prio_list);
+}
+
+static void plist_kunit_del_middle_bucket(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 2);
+	plist_node_init(&c, 3);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	/* Removing the only node of a middle bucket splices the ring. */
+	plist_del(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_TRUE(test, plist_node_empty(&b));
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_PTR_EQ(test, a.prio_list.next, &c.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, c.prio_list.next, &a.prio_list);
+}
+
+/*
+ * Deleting the head of the last priority bucket: its follower takes over
+ * the prio_list entry, and the ring of bucket heads has to wrap round to
+ * the first bucket.
+ */
+static void plist_kunit_del_last_bucket_head(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 2);
+	plist_node_init(&c, 2);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	plist_del(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_TRUE(test, plist_node_empty(&b));
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_FALSE(test, list_empty(&c.prio_list));
+	KUNIT_EXPECT_PTR_EQ(test, a.prio_list.next, &c.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, c.prio_list.next, &a.prio_list);
+}
+
+static void plist_kunit_requeue_basic(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 2);
+	plist_node_init(&c, 2);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	/* A node whose successor has a different priority is left alone. */
+	plist_requeue(&a, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+
+	/* Requeueing moves a node after its same-priority peers. */
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&c), &b);
+
+	/* Requeueing the last node of the list is a no-op. */
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &b);
+
+	/* ...and the peers rotate back. */
+	plist_requeue(&c, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &b);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &c);
+}
+
+/*
+ * The plist_requeue() shortcut: after plist_del() the node's same-priority
+ * successor took over the prio_list entry, so the node can be inserted
+ * directly before the next bucket instead of walking the node_list.
+ */
+static void plist_kunit_requeue_shortcut(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c, d;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 2);
+	plist_node_init(&c, 2);
+	plist_node_init(&d, 3);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+	plist_add(&d, &head);
+
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 4);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&c), &b);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &d);
+
+	/* The tail of its bucket has a different priority: no-op. */
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 4);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&c), &b);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &d);
+}
+
+/*
+ * Regression test for commit 43c91b152243 ("lib/plist: fix
+ * plist_requeue() corrupting order in the last bucket"): when the
+ * requeued node heads the last priority bucket, the prio_list ring of the
+ * bucket heads wraps round to the first bucket, and the node must end up
+ * at the very end of the list instead of jumping to its front.
+ */
+static void plist_kunit_requeue_last_bucket(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c, d, e;
+
+	plist_node_init(&a, 0);
+	plist_node_init(&b, 1);
+	plist_node_init(&c, 1);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &b);
+
+	/* The same wrap with an intermediate bucket before the last one. */
+	plist_node_init(&d, 2);
+	plist_node_init(&e, 2);
+	plist_add(&d, &head);
+	plist_add(&e, &head);
+
+	plist_requeue(&d, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 5);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &e);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &d);
+}
+
+/*
+ * Requeueing the head of the first priority bucket: its follower takes
+ * over the bucket, so the node moves behind it and the new first bucket
+ * has to be linked in the prio_list ring instead.
+ */
+static void plist_kunit_requeue_first_bucket_head(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 1);
+	plist_node_init(&b, 1);
+	plist_node_init(&c, 2);
+
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+	plist_add(&c, &head);
+
+	plist_requeue(&a, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &b);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&b), &a);
+	KUNIT_EXPECT_PTR_EQ(test, plist_next(&a), &c);
+	KUNIT_EXPECT_TRUE(test, list_empty(&a.prio_list));
+	KUNIT_EXPECT_FALSE(test, list_empty(&b.prio_list));
+	KUNIT_EXPECT_PTR_EQ(test, b.prio_list.next, &c.prio_list);
+	KUNIT_EXPECT_PTR_EQ(test, c.prio_list.next, &b.prio_list);
+}
+
+static void plist_kunit_first_last_entry(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_kunit_item items[3];
+	struct plist_kunit_item *first, *last;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(items); i++) {
+		items[i].id = i;
+		plist_node_init(&items[i].node, i + 1);
+		plist_add(&items[i].node, &head);
+	}
+
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 3);
+
+	first = plist_first_entry(&head, struct plist_kunit_item, node);
+	last = plist_last_entry(&head, struct plist_kunit_item, node);
+	KUNIT_EXPECT_EQ(test, first->id, 0);
+	KUNIT_EXPECT_EQ(test, last->id, 2);
+
+	KUNIT_EXPECT_EQ(test, plist_next(&items[0].node)->prio, 2);
+	KUNIT_EXPECT_EQ(test, plist_prev(&items[2].node)->prio, 2);
+}
+
+/*
+ * Deleting and requeueing the priority extremes, including handing the
+ * prio_list ring over when the INT_MIN bucket head goes away.
+ */
+static void plist_kunit_extremes_ops(struct kunit *test)
+{
+	PLIST_HEAD(head);
+	struct plist_node a, b, c;
+
+	plist_node_init(&a, 0);
+	plist_node_init(&b, INT_MAX);
+	plist_node_init(&c, INT_MIN);
+
+	plist_add(&c, &head);
+	plist_add(&a, &head);
+	plist_add(&b, &head);
+
+	/* Deleting the INT_MIN head hands the ring over to the next bucket. */
+	plist_del(&c, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_TRUE(test, plist_node_empty(&c));
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+
+	/* Requeueing the INT_MAX tail node is a no-op. */
+	plist_requeue(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 2);
+	KUNIT_EXPECT_PTR_EQ(test, plist_last(&head), &b);
+
+	/* Deleting the last bucket leaves a single-bucket list. */
+	plist_del(&b, &head);
+	KUNIT_EXPECT_EQ(test, plist_kunit_verify(test, &head), 1);
+	KUNIT_EXPECT_PTR_EQ(test, plist_first(&head), &a);
+	KUNIT_EXPECT_TRUE(test, list_empty(&a.prio_list));
+}
+
+/*
+ * Deterministic pseudo-random add/del/requeue churn, mirroring the
+ * in-module test under CONFIG_DEBUG_PLIST but checking the invariants
+ * after every operation with KUnit assertions instead of BUG_ON().
+ *
+ * Priorities are drawn from a small range so that the list actually
+ * contains multi-node priority buckets, and the requeue step picks a node
+ * that has a same-priority successor -- only such a node moves, so every
+ * requeue in the loop is a real move and its result is checked.
+ */
+static void plist_kunit_random(struct kunit *test)
+{
+	struct plist_node nodes[24];
+	PLIST_HEAD(head);
+	unsigned int r = 12345;
+	int i, loop, nr_expect = 0, nr_requeued = 0;
+
+	for (i = 0; i < ARRAY_SIZE(nodes); i++)
+		plist_node_init(&nodes[i], 0);
+
+	for (loop = 0; loop < 1000; loop++) {
+		r = r * 193939u % 47629u;
+		i = r % ARRAY_SIZE(nodes);
+		if (plist_node_empty(&nodes[i])) {
+			r = r * 193939u % 47629u;
+			plist_node_init(&nodes[i], r % 8);
+			plist_add(&nodes[i], &head);
+			nr_expect++;
+		} else {
+			plist_del(&nodes[i], &head);
+			nr_expect--;
+		}
+
+		KUNIT_ASSERT_EQ(test, plist_kunit_verify(test, &head),
+				nr_expect);
+
+		for (i = 0; i < ARRAY_SIZE(nodes); i++) {
+			struct plist_node *next;
+
+			if (plist_node_empty(&nodes[i]) ||
+			    &nodes[i] == plist_last(&head))
+				continue;
+			next = plist_next(&nodes[i]);
+			if (next->prio != nodes[i].prio)
+				continue;
+
+			plist_requeue(&nodes[i], &head);
+			nr_requeued++;
+
+			KUNIT_ASSERT_EQ(test,
+					plist_kunit_verify(test, &head),
+					nr_expect);
+			/* A requeued node is now the tail of its bucket. */
+			if (&nodes[i] != plist_last(&head))
+				KUNIT_EXPECT_NE(test, nodes[i].prio,
+						plist_next(&nodes[i])->prio);
+			break;
+		}
+	}
+
+	/* The requeue part of the churn must actually have run. */
+	KUNIT_EXPECT_GT(test, nr_requeued, 0);
+
+	for (i = 0; i < ARRAY_SIZE(nodes); i++) {
+		if (plist_node_empty(&nodes[i]))
+			continue;
+		plist_del(&nodes[i], &head);
+		nr_expect--;
+		KUNIT_ASSERT_EQ(test, plist_kunit_verify(test, &head),
+				nr_expect);
+	}
+
+	KUNIT_EXPECT_TRUE(test, plist_head_empty(&head));
+}
+
+static struct kunit_case plist_kunit_test_cases[] = {
+	KUNIT_CASE(plist_kunit_add_empty),
+	KUNIT_CASE(plist_kunit_add_order),
+	KUNIT_CASE(plist_kunit_add_duplicates),
+	KUNIT_CASE(plist_kunit_add_ascending),
+	KUNIT_CASE(plist_kunit_add_descending),
+	KUNIT_CASE(plist_kunit_add_extremes),
+	KUNIT_CASE(plist_kunit_del_bucket_head),
+	KUNIT_CASE(plist_kunit_del_bucket_follower),
+	KUNIT_CASE(plist_kunit_del_middle_bucket),
+	KUNIT_CASE(plist_kunit_del_last_bucket_head),
+	KUNIT_CASE(plist_kunit_requeue_basic),
+	KUNIT_CASE(plist_kunit_requeue_shortcut),
+	KUNIT_CASE(plist_kunit_requeue_last_bucket),
+	KUNIT_CASE(plist_kunit_requeue_first_bucket_head),
+	KUNIT_CASE(plist_kunit_first_last_entry),
+	KUNIT_CASE(plist_kunit_extremes_ops),
+	KUNIT_CASE(plist_kunit_random),
+	{}
+};
+
+static struct kunit_suite plist_kunit_test_suite = {
+	.name = "plist",
+	.test_cases = plist_kunit_test_cases,
+};
+
+kunit_test_suite(plist_kunit_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the priority sorted list (plist)");
+MODULE_LICENSE("GPL");
-- 
2.43.0




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

* Re: [PATCH] lib/tests: add KUnit tests for the priority sorted list (plist)
  2026-11-03 11:36 [PATCH] lib/tests: add KUnit tests for the priority sorted list (plist) Yuehong Wu
@ 2026-11-05 14:30 ` kernel test robot
  0 siblings, 0 replies; 2+ messages in thread
From: kernel test robot @ 2026-11-05 14:30 UTC (permalink / raw)
  To: Yuehong Wu, linux-kernel
  Cc: oe-kbuild-all, akpm, richard120310, handyhandyman.adam, tglx

Hi Yuehong,

kernel test robot noticed the following build errors:

[auto build test ERROR on akpm-mm/mm-nonmm-unstable]
[also build test ERROR on akpm-mm/mm-everything linus/master v7.4-rc1 next-20261030]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Yuehong-Wu/lib-tests-add-KUnit-tests-for-the-priority-sorted-list-plist/20261103-193652
base:   https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-nonmm-unstable
patch link:    https://lore.kernel.org/r/20261103-plist-kunit-1-yuehongwu%40cmss.chinamobile.com
patch subject: [PATCH] lib/tests: add KUnit tests for the priority sorted list (plist)
config: i386-randconfig-1003-20261105 (https://download.01.org/0day-ci/archive/20261105/202611052109.GqbzRkv1-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261105/202611052109.GqbzRkv1-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611052109.GqbzRkv1-lkp@intel.com/

All errors (new ones prefixed by >>, old ones prefixed by <<):

>> ERROR: modpost: lib/tests/plist_kunit.ko: symbol 'plist_add' undefined!
>> ERROR: modpost: lib/tests/plist_kunit.ko: symbol 'plist_del' undefined!
>> ERROR: modpost: lib/tests/plist_kunit.ko: symbol 'plist_requeue' undefined!

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

end of thread, other threads:[~2026-11-05 14:30 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 11:36 [PATCH] lib/tests: add KUnit tests for the priority sorted list (plist) Yuehong Wu
2026-11-05 14:30 ` kernel test robot

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

all inboxes | Powered by JetHome®