* [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