Linux block layer
 help / color / mirror / Atom feed
* [PATCH] lib/sbitmap: add KUnit test suite
@ 2026-10-14  8:27 Wang Yibo
  2026-10-15 14:04 ` David Gow
  0 siblings, 1 reply; 5+ messages in thread
From: Wang Yibo @ 2026-10-14  8:27 UTC (permalink / raw)
  To: Jens Axboe
  Cc: Andrew Morton, linux-block, linux-kernel, linux-kselftest,
	kunit-dev

sbitmap is the tag allocator used by blk-mq and the I/O schedulers
built on top of it, but the plain, synchronous part of its API has no
test coverage at all.

Add a KUnit suite covering initialisation, allocation and exhaustion,
deferred clears, round-robin ordering, resize, the bitmap helpers and
sbitmap_for_each_set(), so that the behaviour is checked on every boot.

The new file is added to the BLOCK LAYER entry in MAINTAINERS, next to
lib/sbitmap.c, so that it does not end up without a maintainer.

Signed-off-by: Wang Yibo <szwyb123@gmail.com>
---
 MAINTAINERS               |   1 +
 lib/Kconfig.debug         |  13 ++
 lib/tests/Makefile        |   1 +
 lib/tests/sbitmap_kunit.c | 300 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 315 insertions(+)
 create mode 100644 lib/tests/sbitmap_kunit.c

diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..c8510e6f1817 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4726,6 +4726,7 @@ F:	include/uapi/linux/blk*
 F:	include/uapi/linux/ioprio.h
 F:	kernel/trace/blktrace.c
 F:	lib/sbitmap.c
+F:	lib/tests/sbitmap_kunit.c
 
 BLOCK LAYER DEVICE DRIVER API [RUST]
 M:	Andreas Hindborg <a.hindborg@kernel.org>
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 6b24a2c89b84..f5fe7f5a0a4d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2456,6 +2456,19 @@ config PRINTF_KUNIT_TEST
 
 	  If unsure, say N.
 
+config SBITMAP_KUNIT_TEST
+	tristate "KUnit test for sbitmap" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	depends on SBITMAP
+	default KUNIT_ALL_TESTS
+	help
+	  Enable to turn on sbitmap tests, running at boot or module load time.
+
+	  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 SCANF_KUNIT_TEST
 	tristate "KUnit test scanf() family of functions at runtime" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 3cac3b63a752..1f49555f94c3 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_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
+obj-$(CONFIG_SBITMAP_KUNIT_TEST) += sbitmap_kunit.o
 obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o
 obj-$(CONFIG_SEQ_BUF_KUNIT_TEST) += seq_buf_kunit.o
 obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o
diff --git a/lib/tests/sbitmap_kunit.c b/lib/tests/sbitmap_kunit.c
new file mode 100644
index 000000000000..4f3a588d15df
--- /dev/null
+++ b/lib/tests/sbitmap_kunit.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for the scalable bitmap (sbitmap) implementation.
+ *
+ * sbitmap is the tag allocator used by blk-mq and the I/O schedulers built on
+ * top of it, but it has no tests.  These tests pin down the observable
+ * behaviour of the plain, synchronous parts of the API.
+ *
+ * Copyright (C) 2026 Wang Yibo <szwyb123@gmail.com>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/gfp.h>
+#include <linux/sbitmap.h>
+
+#include <kunit/test.h>
+
+/*
+ * Big enough to span several words for any plausible BITS_PER_LONG, and not a
+ * power of two so that the last word is partially used.
+ */
+#define SBITMAP_TEST_DEPTH	(3 * BITS_PER_LONG + 5)
+
+static void sbitmap_test_init(struct kunit *test)
+{
+	const unsigned int depth = 97;
+	struct sbitmap sb;
+	int ret;
+
+	ret = sbitmap_init_node(&sb, depth, -1, GFP_KERNEL, NUMA_NO_NODE,
+				false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth);
+	KUNIT_EXPECT_EQ(test, sb.shift,
+			(unsigned int)sbitmap_calculate_shift(depth));
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << sb.shift));
+	KUNIT_EXPECT_FALSE(test, sb.round_robin);
+	KUNIT_EXPECT_NOT_NULL(test, sb.alloc_hint);
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
+	sbitmap_free(&sb);
+
+	/* An explicitly requested shift is honoured. */
+	ret = sbitmap_init_node(&sb, depth, 3, GFP_KERNEL, NUMA_NO_NODE,
+				false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.shift, 3U);
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << 3));
+	sbitmap_free(&sb);
+
+	/* BITS_PER_LONG bits per word is the largest representable granularity. */
+	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG), GFP_KERNEL,
+				NUMA_NO_NODE, false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.shift, (unsigned int)ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, BITS_PER_LONG));
+	sbitmap_free(&sb);
+
+	/* Anything above that cannot be represented and must be rejected. */
+	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG) + 1,
+				GFP_KERNEL, NUMA_NO_NODE, false, true);
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static void sbitmap_test_calculate_shift(struct kunit *test)
+{
+	/* Maps too small to spread over several words keep the default. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(0), ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(1), ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(3), ilog2(BITS_PER_LONG));
+
+	/* Four bits map to one bit per word. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4), 0);
+
+	/* The shift grows back as the depth increases. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG - 1),
+			ilog2(BITS_PER_LONG) - 1);
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG),
+			ilog2(BITS_PER_LONG));
+}
+
+static void sbitmap_test_get_exhaust(struct kunit *test)
+{
+	DECLARE_BITMAP(seen, SBITMAP_TEST_DEPTH) = { 0 };
+	struct sbitmap sb;
+	unsigned int i;
+	int bit;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	/* Every bit is handed out exactly once, then the map is empty. */
+	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
+		bit = sbitmap_get(&sb);
+		KUNIT_ASSERT_GE(test, bit, 0);
+		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
+		KUNIT_ASSERT_FALSE(test, test_bit(bit, seen));
+		__set_bit(bit, seen);
+	}
+
+	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+			(unsigned int)SBITMAP_TEST_DEPTH);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_put_reuse(struct kunit *test)
+{
+	struct sbitmap sb;
+	unsigned int i;
+	int bit;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, BITS_PER_LONG, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						true, true), 0);
+
+	for (i = 0; i < BITS_PER_LONG; i++)
+		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
+
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)BITS_PER_LONG);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	/*
+	 * Freeing a single bit must make exactly that bit available again, no
+	 * matter which bit it was.
+	 */
+	for (i = 0; i < BITS_PER_LONG; i++) {
+		sbitmap_put(&sb, i);
+		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+				(unsigned int)BITS_PER_LONG - 1);
+		bit = sbitmap_get(&sb);
+		KUNIT_EXPECT_EQ(test, bit, (int)i);
+		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+				(unsigned int)BITS_PER_LONG);
+		KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+	}
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_round_robin(struct kunit *test)
+{
+	struct sbitmap sb;
+	unsigned int i;
+	int bit, prev = -1;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						true, true), 0);
+
+	/*
+	 * round_robin must hand out strictly increasing bit numbers while the
+	 * map fills up from empty.
+	 */
+	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
+		bit = sbitmap_get(&sb);
+		KUNIT_ASSERT_GE(test, bit, 0);
+		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
+		if (prev != -1)
+			KUNIT_ASSERT_GT(test, bit, prev);
+		prev = bit;
+	}
+
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+			(unsigned int)SBITMAP_TEST_DEPTH);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_resize(struct kunit *test)
+{
+	const unsigned int depth = 4 * BITS_PER_LONG;
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, depth, -1, GFP_KERNEL,
+						NUMA_NO_NODE, true, true), 0);
+
+	for (i = 0; i < depth; i++)
+		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
+
+	/* Shrinking hides the bits past the new depth. */
+	sbitmap_resize(&sb, depth / 2);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth / 2);
+	KUNIT_EXPECT_EQ(test, sb.map_nr,
+			DIV_ROUND_UP(depth / 2, 1U << sb.shift));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth / 2);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	/* Growing makes them visible again; they are still allocated. */
+	sbitmap_resize(&sb, depth);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth);
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_set_clear_bit(struct kunit *test)
+{
+	static const unsigned int bits[] = {
+		0, 1, BITS_PER_LONG - 1, BITS_PER_LONG,
+		2 * BITS_PER_LONG + 3, SBITMAP_TEST_DEPTH - 1
+	};
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++) {
+		sbitmap_set_bit(&sb, bits[i]);
+		KUNIT_EXPECT_TRUE(test, sbitmap_test_bit(&sb, bits[i]));
+	}
+	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)ARRAY_SIZE(bits));
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++) {
+		sbitmap_clear_bit(&sb, bits[i]);
+		KUNIT_EXPECT_FALSE(test, sbitmap_test_bit(&sb, bits[i]));
+	}
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
+
+	sbitmap_free(&sb);
+}
+
+struct sbitmap_iter_ctx {
+	struct kunit *test;
+	DECLARE_BITMAP(visited, SBITMAP_TEST_DEPTH);
+	unsigned int nr_visited;
+};
+
+static bool sbitmap_iter_collect(struct sbitmap *sb, unsigned int bitnr,
+				 void *data)
+{
+	struct sbitmap_iter_ctx *ctx = data;
+
+	KUNIT_ASSERT_LT(ctx->test, bitnr, SBITMAP_TEST_DEPTH);
+	__set_bit(bitnr, ctx->visited);
+	ctx->nr_visited++;
+
+	return true;
+}
+
+static void sbitmap_test_iterate_set(struct kunit *test)
+{
+	static const unsigned int bits[] = {
+		0, 5, BITS_PER_LONG - 1, BITS_PER_LONG, 130,
+		SBITMAP_TEST_DEPTH - 1
+	};
+	struct sbitmap_iter_ctx ctx = { .test = test };
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++)
+		sbitmap_set_bit(&sb, bits[i]);
+
+	sbitmap_for_each_set(&sb, sbitmap_iter_collect, &ctx);
+
+	KUNIT_EXPECT_EQ(test, ctx.nr_visited, (unsigned int)ARRAY_SIZE(bits));
+	for (i = 0; i < ARRAY_SIZE(bits); i++)
+		KUNIT_EXPECT_TRUE(test, test_bit(bits[i], ctx.visited));
+
+	sbitmap_free(&sb);
+}
+
+static struct kunit_case sbitmap_test_cases[] = {
+	KUNIT_CASE(sbitmap_test_init),
+	KUNIT_CASE(sbitmap_test_calculate_shift),
+	KUNIT_CASE(sbitmap_test_get_exhaust),
+	KUNIT_CASE(sbitmap_test_put_reuse),
+	KUNIT_CASE(sbitmap_test_round_robin),
+	KUNIT_CASE(sbitmap_test_resize),
+	KUNIT_CASE(sbitmap_test_set_clear_bit),
+	KUNIT_CASE(sbitmap_test_iterate_set),
+	{}
+};
+
+static struct kunit_suite sbitmap_test_suite = {
+	.name = "sbitmap",
+	.test_cases = sbitmap_test_cases,
+};
+
+kunit_test_suite(sbitmap_test_suite);
+
+MODULE_DESCRIPTION("KUnit test for sbitmap");
+MODULE_LICENSE("GPL");

base-commit: 4056b3c9d3c92ea45955641dbafeb9b960e146ef
-- 
2.25.1


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

* Re: [PATCH] lib/sbitmap: add KUnit test suite
  2026-10-14  8:27 [PATCH] lib/sbitmap: add KUnit test suite Wang Yibo
@ 2026-10-15 14:04 ` David Gow
  2026-10-16  7:32   ` [PATCH v2] " Wang Yibo
  0 siblings, 1 reply; 5+ messages in thread
From: David Gow @ 2026-10-15 14:04 UTC (permalink / raw)
  To: Wang Yibo, Jens Axboe
  Cc: Andrew Morton, linux-block, linux-kernel, linux-kselftest,
	kunit-dev

Le 14/10/2026 à 16:27, Wang Yibo a écrit :
> sbitmap is the tag allocator used by blk-mq and the I/O schedulers
> built on top of it, but the plain, synchronous part of its API has no
> test coverage at all.
> 
> Add a KUnit suite covering initialisation, allocation and exhaustion,
> deferred clears, round-robin ordering, resize, the bitmap helpers and
> sbitmap_for_each_set(), so that the behaviour is checked on every boot.
> 
> The new file is added to the BLOCK LAYER entry in MAINTAINERS, next to
> lib/sbitmap.c, so that it does not end up without a maintainer.
> 
> Signed-off-by: Wang Yibo <szwyb123@gmail.com>
> ---

Thanks for the test. I'll give it a closer look when I'm back from my 
post-plumbers travel, but it seems that this is failing on a number of 
architectures.

In partciular, sb.alloc_hint is annotated __percpu, which alas doesn't 
work with KUNIT_EXPECT_NOT_NULL on, e.g., x86 where it uses a different 
address space (__seg_fs or __seg_gs). This is probably something we 
should work out how to address in KUnit, but for now you may be better 
off with a different assertion.

Equally, sbitmap_test_iterate_set is failing here on 32-bit UML (but 
only when built with gcc, not clang):

# sbitmap_test_iterate_set: EXPECTATION FAILED at 
lib/tests/sbitmap_kunit.c:273
  Expected ctx.nr_visited == (unsigned int)(sizeof(bits) / 
sizeof((bits)[0]) + ((int)sizeof(struct 
{_Static_assert(!(!(!__builtin_types_compatible_p(typeof((bits)), 
typeof(&(bits)[0])))), "must be array");}))), but
      ctx.nr_visited == 5 (0x5)
      (unsigned int)(sizeof(bits) / sizeof((bits)[0]) + 
((int)sizeof(struct 
{_Static_assert(!(!(!__builtin_types_compatible_p(typeof((bits)), 
typeof(&(bits)[0])))), "must be array");}))) == 6 (0x6)
  [FAILED] sbitmap_test_iterate_set
      # module: sbitmap_kunit
  # sbitmap: pass:7 fail:1 skip:0 total:8
# Totals: pass:7 fail:1 skip:0 total:8
===================== [FAILED] sbitmap =====================

Not sure what's causing that yet: it may not be an issue with the test 
itself.

Cheers,
-- David
>   MAINTAINERS               |   1 +
>   lib/Kconfig.debug         |  13 ++
>   lib/tests/Makefile        |   1 +
>   lib/tests/sbitmap_kunit.c | 300 ++++++++++++++++++++++++++++++++++++++
>   4 files changed, 315 insertions(+)
>   create mode 100644 lib/tests/sbitmap_kunit.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f53047aa8d3c..c8510e6f1817 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4726,6 +4726,7 @@ F:	include/uapi/linux/blk*
>   F:	include/uapi/linux/ioprio.h
>   F:	kernel/trace/blktrace.c
>   F:	lib/sbitmap.c
> +F:	lib/tests/sbitmap_kunit.c
>   
>   BLOCK LAYER DEVICE DRIVER API [RUST]
>   M:	Andreas Hindborg <a.hindborg@kernel.org>
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 6b24a2c89b84..f5fe7f5a0a4d 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2456,6 +2456,19 @@ config PRINTF_KUNIT_TEST
>   
>   	  If unsure, say N.
>   
> +config SBITMAP_KUNIT_TEST
> +	tristate "KUnit test for sbitmap" if !KUNIT_ALL_TESTS
> +	depends on KUNIT
> +	depends on SBITMAP
> +	default KUNIT_ALL_TESTS
> +	help
> +	  Enable to turn on sbitmap tests, running at boot or module load time.
> +
> +	  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 SCANF_KUNIT_TEST
>   	tristate "KUnit test scanf() family of functions at runtime" if !KUNIT_ALL_TESTS
>   	depends on KUNIT
> diff --git a/lib/tests/Makefile b/lib/tests/Makefile
> index 3cac3b63a752..1f49555f94c3 100644
> --- a/lib/tests/Makefile
> +++ b/lib/tests/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_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
> +obj-$(CONFIG_SBITMAP_KUNIT_TEST) += sbitmap_kunit.o
>   obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o
>   obj-$(CONFIG_SEQ_BUF_KUNIT_TEST) += seq_buf_kunit.o
>   obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o
> diff --git a/lib/tests/sbitmap_kunit.c b/lib/tests/sbitmap_kunit.c
> new file mode 100644
> index 000000000000..4f3a588d15df
> --- /dev/null
> +++ b/lib/tests/sbitmap_kunit.c
> @@ -0,0 +1,300 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * KUnit tests for the scalable bitmap (sbitmap) implementation.
> + *
> + * sbitmap is the tag allocator used by blk-mq and the I/O schedulers built on
> + * top of it, but it has no tests.  These tests pin down the observable
> + * behaviour of the plain, synchronous parts of the API.
> + *
> + * Copyright (C) 2026 Wang Yibo <szwyb123@gmail.com>
> + */
> +
> +#include <linux/bitmap.h>
> +#include <linux/gfp.h>
> +#include <linux/sbitmap.h>
> +
> +#include <kunit/test.h>
> +
> +/*
> + * Big enough to span several words for any plausible BITS_PER_LONG, and not a
> + * power of two so that the last word is partially used.
> + */
> +#define SBITMAP_TEST_DEPTH	(3 * BITS_PER_LONG + 5)
> +
> +static void sbitmap_test_init(struct kunit *test)
> +{
> +	const unsigned int depth = 97;
> +	struct sbitmap sb;
> +	int ret;
> +
> +	ret = sbitmap_init_node(&sb, depth, -1, GFP_KERNEL, NUMA_NO_NODE,
> +				false, true);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, sb.depth, depth);
> +	KUNIT_EXPECT_EQ(test, sb.shift,
> +			(unsigned int)sbitmap_calculate_shift(depth));
> +	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << sb.shift));
> +	KUNIT_EXPECT_FALSE(test, sb.round_robin);
> +	KUNIT_EXPECT_NOT_NULL(test, sb.alloc_hint);

alloc_hint is declared __percpu, which is incompatible with 
KUNIT_EXPECT_NOT_NULL() on x86.

> +	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
> +	sbitmap_free(&sb);
> +
> +	/* An explicitly requested shift is honoured. */
> +	ret = sbitmap_init_node(&sb, depth, 3, GFP_KERNEL, NUMA_NO_NODE,
> +				false, true);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, sb.shift, 3U);
> +	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << 3));
> +	sbitmap_free(&sb);
> +
> +	/* BITS_PER_LONG bits per word is the largest representable granularity. */
> +	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG), GFP_KERNEL,
> +				NUMA_NO_NODE, false, true);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, sb.shift, (unsigned int)ilog2(BITS_PER_LONG));
> +	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, BITS_PER_LONG));
> +	sbitmap_free(&sb);
> +
> +	/* Anything above that cannot be represented and must be rejected. */
> +	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG) + 1,
> +				GFP_KERNEL, NUMA_NO_NODE, false, true);
> +	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
> +}
> +
> +static void sbitmap_test_calculate_shift(struct kunit *test)
> +{
> +	/* Maps too small to spread over several words keep the default. */
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(0), ilog2(BITS_PER_LONG));
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(1), ilog2(BITS_PER_LONG));
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(3), ilog2(BITS_PER_LONG));
> +
> +	/* Four bits map to one bit per word. */
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4), 0);
> +
> +	/* The shift grows back as the depth increases. */
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG - 1),
> +			ilog2(BITS_PER_LONG) - 1);
> +	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG),
> +			ilog2(BITS_PER_LONG));
> +}
> +
> +static void sbitmap_test_get_exhaust(struct kunit *test)
> +{
> +	DECLARE_BITMAP(seen, SBITMAP_TEST_DEPTH) = { 0 };
> +	struct sbitmap sb;
> +	unsigned int i;
> +	int bit;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
> +						GFP_KERNEL, NUMA_NO_NODE,
> +						false, true), 0);
> +
> +	/* Every bit is handed out exactly once, then the map is empty. */
> +	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
> +		bit = sbitmap_get(&sb);
> +		KUNIT_ASSERT_GE(test, bit, 0);
> +		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
> +		KUNIT_ASSERT_FALSE(test, test_bit(bit, seen));
> +		__set_bit(bit, seen);
> +	}
> +
> +	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
> +			(unsigned int)SBITMAP_TEST_DEPTH);
> +	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +
> +	sbitmap_free(&sb);
> +}
> +
> +static void sbitmap_test_put_reuse(struct kunit *test)
> +{
> +	struct sbitmap sb;
> +	unsigned int i;
> +	int bit;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, BITS_PER_LONG, -1,
> +						GFP_KERNEL, NUMA_NO_NODE,
> +						true, true), 0);
> +
> +	for (i = 0; i < BITS_PER_LONG; i++)
> +		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
> +
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)BITS_PER_LONG);
> +	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +
> +	/*
> +	 * Freeing a single bit must make exactly that bit available again, no
> +	 * matter which bit it was.
> +	 */
> +	for (i = 0; i < BITS_PER_LONG; i++) {
> +		sbitmap_put(&sb, i);
> +		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
> +				(unsigned int)BITS_PER_LONG - 1);
> +		bit = sbitmap_get(&sb);
> +		KUNIT_EXPECT_EQ(test, bit, (int)i);
> +		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
> +				(unsigned int)BITS_PER_LONG);
> +		KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +	}
> +
> +	sbitmap_free(&sb);
> +}
> +
> +static void sbitmap_test_round_robin(struct kunit *test)
> +{
> +	struct sbitmap sb;
> +	unsigned int i;
> +	int bit, prev = -1;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
> +						GFP_KERNEL, NUMA_NO_NODE,
> +						true, true), 0);
> +
> +	/*
> +	 * round_robin must hand out strictly increasing bit numbers while the
> +	 * map fills up from empty.
> +	 */
> +	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
> +		bit = sbitmap_get(&sb);
> +		KUNIT_ASSERT_GE(test, bit, 0);
> +		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
> +		if (prev != -1)
> +			KUNIT_ASSERT_GT(test, bit, prev);
> +		prev = bit;
> +	}
> +
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
> +			(unsigned int)SBITMAP_TEST_DEPTH);
> +	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +
> +	sbitmap_free(&sb);
> +}
> +
> +static void sbitmap_test_resize(struct kunit *test)
> +{
> +	const unsigned int depth = 4 * BITS_PER_LONG;
> +	struct sbitmap sb;
> +	unsigned int i;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, depth, -1, GFP_KERNEL,
> +						NUMA_NO_NODE, true, true), 0);
> +
> +	for (i = 0; i < depth; i++)
> +		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
> +
> +	/* Shrinking hides the bits past the new depth. */
> +	sbitmap_resize(&sb, depth / 2);
> +	KUNIT_EXPECT_EQ(test, sb.depth, depth / 2);
> +	KUNIT_EXPECT_EQ(test, sb.map_nr,
> +			DIV_ROUND_UP(depth / 2, 1U << sb.shift));
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth / 2);
> +	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +
> +	/* Growing makes them visible again; they are still allocated. */
> +	sbitmap_resize(&sb, depth);
> +	KUNIT_EXPECT_EQ(test, sb.depth, depth);
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
> +	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
> +
> +	sbitmap_free(&sb);
> +}
> +
> +static void sbitmap_test_set_clear_bit(struct kunit *test)
> +{
> +	static const unsigned int bits[] = {
> +		0, 1, BITS_PER_LONG - 1, BITS_PER_LONG,
> +		2 * BITS_PER_LONG + 3, SBITMAP_TEST_DEPTH - 1
> +	};
> +	struct sbitmap sb;
> +	unsigned int i;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
> +						GFP_KERNEL, NUMA_NO_NODE,
> +						false, true), 0);
> +
> +	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
> +
> +	for (i = 0; i < ARRAY_SIZE(bits); i++) {
> +		sbitmap_set_bit(&sb, bits[i]);
> +		KUNIT_EXPECT_TRUE(test, sbitmap_test_bit(&sb, bits[i]));
> +	}
> +	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)ARRAY_SIZE(bits));
> +
> +	for (i = 0; i < ARRAY_SIZE(bits); i++) {
> +		sbitmap_clear_bit(&sb, bits[i]);
> +		KUNIT_EXPECT_FALSE(test, sbitmap_test_bit(&sb, bits[i]));
> +	}
> +	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
> +	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
> +
> +	sbitmap_free(&sb);
> +}
> +
> +struct sbitmap_iter_ctx {
> +	struct kunit *test;
> +	DECLARE_BITMAP(visited, SBITMAP_TEST_DEPTH);
> +	unsigned int nr_visited;
> +};
> +
> +static bool sbitmap_iter_collect(struct sbitmap *sb, unsigned int bitnr,
> +				 void *data)
> +{
> +	struct sbitmap_iter_ctx *ctx = data;
> +
> +	KUNIT_ASSERT_LT(ctx->test, bitnr, SBITMAP_TEST_DEPTH);
> +	__set_bit(bitnr, ctx->visited);
> +	ctx->nr_visited++;
> +
> +	return true;
> +}
> +
> +static void sbitmap_test_iterate_set(struct kunit *test)
> +{
> +	static const unsigned int bits[] = {
> +		0, 5, BITS_PER_LONG - 1, BITS_PER_LONG, 130,
> +		SBITMAP_TEST_DEPTH - 1
> +	};
> +	struct sbitmap_iter_ctx ctx = { .test = test };
> +	struct sbitmap sb;
> +	unsigned int i;
> +
> +	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
> +						GFP_KERNEL, NUMA_NO_NODE,
> +						false, true), 0);
> +
> +	for (i = 0; i < ARRAY_SIZE(bits); i++)
> +		sbitmap_set_bit(&sb, bits[i]);
> +
> +	sbitmap_for_each_set(&sb, sbitmap_iter_collect, &ctx);
> +
> +	KUNIT_EXPECT_EQ(test, ctx.nr_visited, (unsigned int)ARRAY_SIZE(bits));

This fails on 32-bit UML w/ gcc 16.2.1 (SUSE): nr_visited == 5, 
ARRAY_SIZE(bits) == 7.

Seems to be passing with clang 23.1.2, though.

> +	for (i = 0; i < ARRAY_SIZE(bits); i++)
> +		KUNIT_EXPECT_TRUE(test, test_bit(bits[i], ctx.visited));
> +
> +	sbitmap_free(&sb);
> +}
> +
> +static struct kunit_case sbitmap_test_cases[] = {
> +	KUNIT_CASE(sbitmap_test_init),
> +	KUNIT_CASE(sbitmap_test_calculate_shift),
> +	KUNIT_CASE(sbitmap_test_get_exhaust),
> +	KUNIT_CASE(sbitmap_test_put_reuse),
> +	KUNIT_CASE(sbitmap_test_round_robin),
> +	KUNIT_CASE(sbitmap_test_resize),
> +	KUNIT_CASE(sbitmap_test_set_clear_bit),
> +	KUNIT_CASE(sbitmap_test_iterate_set),
> +	{}
> +};
> +
> +static struct kunit_suite sbitmap_test_suite = {
> +	.name = "sbitmap",
> +	.test_cases = sbitmap_test_cases,
> +};
> +
> +kunit_test_suite(sbitmap_test_suite);
> +
> +MODULE_DESCRIPTION("KUnit test for sbitmap");
> +MODULE_LICENSE("GPL");
> 
> base-commit: 4056b3c9d3c92ea45955641dbafeb9b960e146ef


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

* [PATCH v2] lib/sbitmap: add KUnit test suite
  2026-10-15 14:04 ` David Gow
@ 2026-10-16  7:32   ` Wang Yibo
  2026-10-16 16:22     ` shinichiro.kawasaki
  0 siblings, 1 reply; 5+ messages in thread
From: Wang Yibo @ 2026-10-16  7:32 UTC (permalink / raw)
  To: Jens Axboe, David Gow
  Cc: Andrew Morton, linux-block, linux-kernel, linux-kselftest,
	kunit-dev

sbitmap is the tag allocator used by blk-mq and the I/O schedulers
built on top of it, but the plain, synchronous part of its API has no
test coverage at all.

Add a KUnit suite covering initialisation, allocation and exhaustion,
deferred clears, round-robin ordering, resize, the bitmap helpers and
sbitmap_for_each_set(), so that the behaviour is checked on every boot.

The new file is added to the BLOCK LAYER entry in MAINTAINERS, next to
lib/sbitmap.c, so that it does not end up without a maintainer.

Signed-off-by: Wang Yibo <szwyb123@gmail.com>
---
v2:
 - Test sb.alloc_hint as a plain boolean: KUNIT_EXPECT_NOT_NULL() passes
   its argument through a const void *, which a __percpu pointer cannot do
   on architectures that keep percpu data in a separate address space, so
   the test did not build on x86 (David Gow).
 - Use a BITS_PER_LONG-relative bit number in sbitmap_test_iterate_set().
   The hardcoded 130 lies past the end of the test bitmap when
   BITS_PER_LONG is 32, so that bit was never visited and the test failed;
   assert the precondition while at it (David Gow).

Both issues were reproduced and the fixes verified:
  - x86_64 (gcc 15.2, CONFIG_USE_X86_SEG_SUPPORT=y): v1 fails to build
    with "comparison of pointers to disjoint address spaces" at the
    KUNIT_EXPECT_NOT_NULL(sb.alloc_hint); v2 builds clean.
  - i386, i.e. 32-bit (gcc): v1 fails sbitmap_test_iterate_set with
    nr_visited == 5, as the out-of-range bit 130 is never visited;
    v2 passes.
  - v2 runs the whole suite green (8/8) under KUnit/QEMU on arm64,
    i386 and x86_64.

Link: https://lore.kernel.org/r/20261014082704.33667-1-szwyb123@gmail.com
Link: https://lore.kernel.org/r/d6ea208a-0b53-4da8-998f-817a0e0b7c61@davidgow.net
 MAINTAINERS               |   1 +
 lib/Kconfig.debug         |  13 ++
 lib/tests/Makefile        |   1 +
 lib/tests/sbitmap_kunit.c | 308 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 323 insertions(+)
 create mode 100644 lib/tests/sbitmap_kunit.c

diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..c8510e6f1817 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4726,6 +4726,7 @@ F:	include/uapi/linux/blk*
 F:	include/uapi/linux/ioprio.h
 F:	kernel/trace/blktrace.c
 F:	lib/sbitmap.c
+F:	lib/tests/sbitmap_kunit.c
 
 BLOCK LAYER DEVICE DRIVER API [RUST]
 M:	Andreas Hindborg <a.hindborg@kernel.org>
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 6b24a2c89b84..f5fe7f5a0a4d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2456,6 +2456,19 @@ config PRINTF_KUNIT_TEST
 
 	  If unsure, say N.
 
+config SBITMAP_KUNIT_TEST
+	tristate "KUnit test for sbitmap" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	depends on SBITMAP
+	default KUNIT_ALL_TESTS
+	help
+	  Enable to turn on sbitmap tests, running at boot or module load time.
+
+	  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 SCANF_KUNIT_TEST
 	tristate "KUnit test scanf() family of functions at runtime" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 3cac3b63a752..1f49555f94c3 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_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
+obj-$(CONFIG_SBITMAP_KUNIT_TEST) += sbitmap_kunit.o
 obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o
 obj-$(CONFIG_SEQ_BUF_KUNIT_TEST) += seq_buf_kunit.o
 obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o
diff --git a/lib/tests/sbitmap_kunit.c b/lib/tests/sbitmap_kunit.c
new file mode 100644
index 000000000000..284dcb5d7188
--- /dev/null
+++ b/lib/tests/sbitmap_kunit.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for the scalable bitmap (sbitmap) implementation.
+ *
+ * sbitmap is the tag allocator used by blk-mq and the I/O schedulers built on
+ * top of it, but it has no tests.  These tests pin down the observable
+ * behaviour of the plain, synchronous parts of the API.
+ *
+ * Copyright (C) 2026 Wang Yibo <szwyb123@gmail.com>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/gfp.h>
+#include <linux/sbitmap.h>
+
+#include <kunit/test.h>
+
+/*
+ * Big enough to span several words for any plausible BITS_PER_LONG, and not a
+ * power of two so that the last word is partially used.
+ */
+#define SBITMAP_TEST_DEPTH	(3 * BITS_PER_LONG + 5)
+
+static void sbitmap_test_init(struct kunit *test)
+{
+	const unsigned int depth = 97;
+	struct sbitmap sb;
+	int ret;
+
+	ret = sbitmap_init_node(&sb, depth, -1, GFP_KERNEL, NUMA_NO_NODE,
+				false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth);
+	KUNIT_EXPECT_EQ(test, sb.shift,
+			(unsigned int)sbitmap_calculate_shift(depth));
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << sb.shift));
+	KUNIT_EXPECT_FALSE(test, sb.round_robin);
+	/*
+	 * alloc_hint is a __percpu pointer, which the generic pointer
+	 * assertions cannot take: on x86, where percpu data lives in
+	 * __seg_gs, passing it through a const void * does not compile.
+	 * Test it as a plain boolean instead.
+	 */
+	KUNIT_EXPECT_TRUE(test, sb.alloc_hint);
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
+	sbitmap_free(&sb);
+
+	/* An explicitly requested shift is honoured. */
+	ret = sbitmap_init_node(&sb, depth, 3, GFP_KERNEL, NUMA_NO_NODE,
+				false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.shift, 3U);
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, 1U << 3));
+	sbitmap_free(&sb);
+
+	/* BITS_PER_LONG bits per word is the largest representable granularity. */
+	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG), GFP_KERNEL,
+				NUMA_NO_NODE, false, true);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sb.shift, (unsigned int)ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sb.map_nr, DIV_ROUND_UP(depth, BITS_PER_LONG));
+	sbitmap_free(&sb);
+
+	/* Anything above that cannot be represented and must be rejected. */
+	ret = sbitmap_init_node(&sb, depth, ilog2(BITS_PER_LONG) + 1,
+				GFP_KERNEL, NUMA_NO_NODE, false, true);
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static void sbitmap_test_calculate_shift(struct kunit *test)
+{
+	/* Maps too small to spread over several words keep the default. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(0), ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(1), ilog2(BITS_PER_LONG));
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(3), ilog2(BITS_PER_LONG));
+
+	/* Four bits map to one bit per word. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4), 0);
+
+	/* The shift grows back as the depth increases. */
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG - 1),
+			ilog2(BITS_PER_LONG) - 1);
+	KUNIT_EXPECT_EQ(test, sbitmap_calculate_shift(4 * BITS_PER_LONG),
+			ilog2(BITS_PER_LONG));
+}
+
+static void sbitmap_test_get_exhaust(struct kunit *test)
+{
+	DECLARE_BITMAP(seen, SBITMAP_TEST_DEPTH) = { 0 };
+	struct sbitmap sb;
+	unsigned int i;
+	int bit;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	/* Every bit is handed out exactly once, then the map is empty. */
+	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
+		bit = sbitmap_get(&sb);
+		KUNIT_ASSERT_GE(test, bit, 0);
+		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
+		KUNIT_ASSERT_FALSE(test, test_bit(bit, seen));
+		__set_bit(bit, seen);
+	}
+
+	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+			(unsigned int)SBITMAP_TEST_DEPTH);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_put_reuse(struct kunit *test)
+{
+	struct sbitmap sb;
+	unsigned int i;
+	int bit;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, BITS_PER_LONG, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						true, true), 0);
+
+	for (i = 0; i < BITS_PER_LONG; i++)
+		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
+
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)BITS_PER_LONG);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	/*
+	 * Freeing a single bit must make exactly that bit available again, no
+	 * matter which bit it was.
+	 */
+	for (i = 0; i < BITS_PER_LONG; i++) {
+		sbitmap_put(&sb, i);
+		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+				(unsigned int)BITS_PER_LONG - 1);
+		bit = sbitmap_get(&sb);
+		KUNIT_EXPECT_EQ(test, bit, (int)i);
+		KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+				(unsigned int)BITS_PER_LONG);
+		KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+	}
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_round_robin(struct kunit *test)
+{
+	struct sbitmap sb;
+	unsigned int i;
+	int bit, prev = -1;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						true, true), 0);
+
+	/*
+	 * round_robin must hand out strictly increasing bit numbers while the
+	 * map fills up from empty.
+	 */
+	for (i = 0; i < SBITMAP_TEST_DEPTH; i++) {
+		bit = sbitmap_get(&sb);
+		KUNIT_ASSERT_GE(test, bit, 0);
+		KUNIT_ASSERT_LT(test, bit, (int)SBITMAP_TEST_DEPTH);
+		if (prev != -1)
+			KUNIT_ASSERT_GT(test, bit, prev);
+		prev = bit;
+	}
+
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb),
+			(unsigned int)SBITMAP_TEST_DEPTH);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_resize(struct kunit *test)
+{
+	const unsigned int depth = 4 * BITS_PER_LONG;
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, depth, -1, GFP_KERNEL,
+						NUMA_NO_NODE, true, true), 0);
+
+	for (i = 0; i < depth; i++)
+		KUNIT_ASSERT_GE(test, sbitmap_get(&sb), 0);
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
+
+	/* Shrinking hides the bits past the new depth. */
+	sbitmap_resize(&sb, depth / 2);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth / 2);
+	KUNIT_EXPECT_EQ(test, sb.map_nr,
+			DIV_ROUND_UP(depth / 2, 1U << sb.shift));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth / 2);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	/* Growing makes them visible again; they are still allocated. */
+	sbitmap_resize(&sb, depth);
+	KUNIT_EXPECT_EQ(test, sb.depth, depth);
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), depth);
+	KUNIT_EXPECT_EQ(test, sbitmap_get(&sb), -1);
+
+	sbitmap_free(&sb);
+}
+
+static void sbitmap_test_set_clear_bit(struct kunit *test)
+{
+	static const unsigned int bits[] = {
+		0, 1, BITS_PER_LONG - 1, BITS_PER_LONG,
+		2 * BITS_PER_LONG + 3, SBITMAP_TEST_DEPTH - 1
+	};
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++) {
+		sbitmap_set_bit(&sb, bits[i]);
+		KUNIT_EXPECT_TRUE(test, sbitmap_test_bit(&sb, bits[i]));
+	}
+	KUNIT_EXPECT_TRUE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), (unsigned int)ARRAY_SIZE(bits));
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++) {
+		sbitmap_clear_bit(&sb, bits[i]);
+		KUNIT_EXPECT_FALSE(test, sbitmap_test_bit(&sb, bits[i]));
+	}
+	KUNIT_EXPECT_FALSE(test, sbitmap_any_bit_set(&sb));
+	KUNIT_EXPECT_EQ(test, sbitmap_weight(&sb), 0U);
+
+	sbitmap_free(&sb);
+}
+
+struct sbitmap_iter_ctx {
+	struct kunit *test;
+	DECLARE_BITMAP(visited, SBITMAP_TEST_DEPTH);
+	unsigned int nr_visited;
+};
+
+static bool sbitmap_iter_collect(struct sbitmap *sb, unsigned int bitnr,
+				 void *data)
+{
+	struct sbitmap_iter_ctx *ctx = data;
+
+	KUNIT_ASSERT_LT(ctx->test, bitnr, SBITMAP_TEST_DEPTH);
+	__set_bit(bitnr, ctx->visited);
+	ctx->nr_visited++;
+
+	return true;
+}
+
+static void sbitmap_test_iterate_set(struct kunit *test)
+{
+	static const unsigned int bits[] = {
+		0, 5, BITS_PER_LONG - 1, BITS_PER_LONG, 2 * BITS_PER_LONG + 3,
+		SBITMAP_TEST_DEPTH - 1
+	};
+	struct sbitmap_iter_ctx ctx = { .test = test };
+	struct sbitmap sb;
+	unsigned int i;
+
+	KUNIT_ASSERT_EQ(test, sbitmap_init_node(&sb, SBITMAP_TEST_DEPTH, -1,
+						GFP_KERNEL, NUMA_NO_NODE,
+						false, true), 0);
+
+	for (i = 0; i < ARRAY_SIZE(bits); i++) {
+		KUNIT_ASSERT_LT(test, bits[i], SBITMAP_TEST_DEPTH);
+		sbitmap_set_bit(&sb, bits[i]);
+	}
+
+	sbitmap_for_each_set(&sb, sbitmap_iter_collect, &ctx);
+
+	KUNIT_EXPECT_EQ(test, ctx.nr_visited, (unsigned int)ARRAY_SIZE(bits));
+	for (i = 0; i < ARRAY_SIZE(bits); i++)
+		KUNIT_EXPECT_TRUE(test, test_bit(bits[i], ctx.visited));
+
+	sbitmap_free(&sb);
+}
+
+static struct kunit_case sbitmap_test_cases[] = {
+	KUNIT_CASE(sbitmap_test_init),
+	KUNIT_CASE(sbitmap_test_calculate_shift),
+	KUNIT_CASE(sbitmap_test_get_exhaust),
+	KUNIT_CASE(sbitmap_test_put_reuse),
+	KUNIT_CASE(sbitmap_test_round_robin),
+	KUNIT_CASE(sbitmap_test_resize),
+	KUNIT_CASE(sbitmap_test_set_clear_bit),
+	KUNIT_CASE(sbitmap_test_iterate_set),
+	{}
+};
+
+static struct kunit_suite sbitmap_test_suite = {
+	.name = "sbitmap",
+	.test_cases = sbitmap_test_cases,
+};
+
+kunit_test_suite(sbitmap_test_suite);
+
+MODULE_DESCRIPTION("KUnit test for sbitmap");
+MODULE_LICENSE("GPL");

base-commit: 4056b3c9d3c92ea45955641dbafeb9b960e146ef
-- 
2.25.1


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

* Re: [PATCH v2] lib/sbitmap: add KUnit test suite
  2026-10-16  7:32   ` [PATCH v2] " Wang Yibo
@ 2026-10-16 16:22     ` shinichiro.kawasaki
  2026-10-16 23:17       ` Shin'ichiro Kawasaki
  0 siblings, 1 reply; 5+ messages in thread
From: shinichiro.kawasaki @ 2026-10-16 16:22 UTC (permalink / raw)
  To: linux-block; +Cc: blktests-ci-bot

[-- Attachment #1: Type: text/plain, Size: 324 bytes --]

Dear patch submitter,

Blktests CI has tested the following submission:
Status:     FAILURE
Name:       [v2] lib/sbitmap: add KUnit test suite
Patchwork:  https://patchwork.kernel.org/series/1187055/&state=*
Run record: https://github.com/linux-blktests/linux-block/actions/runs/38868812396


Failed test cases: block/017



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

* Re: [PATCH v2] lib/sbitmap: add KUnit test suite
  2026-10-16 16:22     ` shinichiro.kawasaki
@ 2026-10-16 23:17       ` Shin'ichiro Kawasaki
  0 siblings, 0 replies; 5+ messages in thread
From: Shin'ichiro Kawasaki @ 2026-10-16 23:17 UTC (permalink / raw)
  To: linux-block; +Cc: blktests-ci-bot

On Oct 16, 2026 / 09:22, shinichiro.kawasaki@wdc.com wrote:
> Dear patch submitter,
> 
> Blktests CI has tested the following submission:
> Status:     FAILURE
> Name:       [v2] lib/sbitmap: add KUnit test suite
> Patchwork:  https://patchwork.kernel.org/series/1187055/&state=*
> Run record: https://github.com/linux-blktests/linux-block/actions/runs/38868812396
> 
> 
> Failed test cases: block/017

Please ignore this failure report. I took a look in the test case, but it has no
relation with the patch. The test case measures null_blk I/O performance to
judge pass/fail, and when an exceptional number is measured, the test case can
report false-positive failure, probably. Sorry for the noise.

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

end of thread, other threads:[~2026-10-16 23:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14  8:27 [PATCH] lib/sbitmap: add KUnit test suite Wang Yibo
2026-10-15 14:04 ` David Gow
2026-10-16  7:32   ` [PATCH v2] " Wang Yibo
2026-10-16 16:22     ` shinichiro.kawasaki
2026-10-16 23:17       ` Shin'ichiro Kawasaki

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