mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: David Howells <dhowells@redhat.com>
To: Christian Brauner <christian@brauner.io>
Cc: David Howells <dhowells@redhat.com>,
	Paulo Alcantara <pc@manguebit.org>,
	Matthew Wilcox <willy@infradead.org>,
	Namjae Jeon <linkinjeon@kernel.org>,
	Marc Dionne <marc.dionne@auristor.com>,
	Stefan Metzmacher <metze@samba.org>,
	Eric Van Hensbergen <ericvh@kernel.org>,
	Dominique Martinet <asmadeus@codewreck.org>,
	Ilya Dryomov <idryomov@gmail.com>,
	netfs@lists.linux.dev, linux-afs@lists.infradead.org,
	linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org,
	ceph-devel@vger.kernel.org, v9fs@lists.linux.dev,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Jens Axboe <axboe@kernel.dk>,
	Christoph Hellwig <hch@infradead.org>,
	linux-block@vger.kernel.org
Subject: [PATCH v16 05/15] iov_iter: Add a bvecq kunit test
Date: Sat,  7 Nov 2026 16:08:08 +0000	[thread overview]
Message-ID: <20261107160823.1465553-6-dhowells@redhat.com> (raw)
In-Reply-To: <20261107160823.1465553-1-dhowells@redhat.com>

Add a bvecq kunit test to exercise the bvecq iterator in a bunch of
different ways.  Each kunit test sets up a series of folios of different
sizes (1, 2, 4 or 8 pages) and fills each subpage thereof with a distinct
predictable pattern that it can then sample to check that the iterator
appears to be in the right place.

The folios are then assembled into a bvecq chain depending on a particular
recipe and partialness list and then advance, revert and copy-out is
tested, along with checking iov_iter_npages() at each point.

Each test is a combination of a bvecq chain recipe:

 - A single bvecq with 16 folios with no gaps (zero-len bio_vec).
 - A chain of two bvecqs, each with 8 folios and no gaps.
 - As the previous, but with a gap at the end of the first chain.
 - A chain of four bvecqs, each with 4 folios and scattered gaps,
   including at the front and back.
 - A chain of five bvecqs, 3 with folios and two with no folios, one empty
   and one with a couple of gaps.
 - A chain of seven bvecqs, 3 with folios, 3 completely empty (including
   first and last bvecqs in the chain) and 1 with just a couple of gaps.

and a selection of folio parts:

 - Whole folios only.
 - Scattered parts of folios, with some contiguities.
 - Every folio with the first and last bytes excluded.

and a test algorithm:

 - For each possible starting position in the bvecq, do a small advance,
   check fill, a couple of small reverts, each checked and then advance to
   the next starting position.
 - Starting at the beginning, advance in jumps by a largish step forward,
   checking after each jump; then when the end is hit revert in jumps by a
   slightly larger step back, checking each time; then double the step and
   repeat until the step exceeds the size of the iterator.

iov_iter_alignment() is compared at the beginning of one of the test sets
to a compound of alignments from an ITER_BVEC.  Note that the ITER_BVEC
alignment check malfunctions if given a zero-length bio_vec array.

Exercising extraction, copy-from and copy-to is done in kunit_iov_iter.c,
with those bits added in a different patch.

Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
 MAINTAINERS             |   1 +
 lib/Kconfig.debug       |  13 +
 lib/tests/Makefile      |   1 +
 lib/tests/kunit_bvecq.c | 599 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 614 insertions(+)
 create mode 100644 lib/tests/kunit_bvecq.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9cb3785a5bd2..bdda4ad58ab4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10391,6 +10391,7 @@ F:	include/linux/fscache*.h
 F:	include/linux/netfs.h
 F:	include/trace/events/fscache.h
 F:	include/trace/events/netfs.h
+F:	lib/tests/kunit_bvecq.c
 
 FILESYSTEMS [STACKABLE]
 M:	Miklos Szeredi <miklos@szeredi.hu>
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638ec13a..06b4546421a1 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2385,6 +2385,19 @@ config TEST_IOV_ITER
 
 	  If unsure, say N.
 
+config TEST_BVECQ
+	tristate "Test bvecq operation" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	depends on MMU
+	depends on NETFS_SUPPORT
+	default KUNIT_ALL_TESTS
+	help
+	  Enable this to turn on testing of the operation of the bvecq
+	  structure.  This test is executed only once during system
+	  boot (so affects only boot time), or at module load time.
+
+	  If unsure, say N.
+
 config KPROBES_SANITY_TEST
 	tristate "Kprobes sanity tests" if !KUNIT_ALL_TESTS
 	depends on DEBUG_KERNEL
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index df0537e1b91f..85e3c5e1f7d2 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_GLOB_KUNIT_TEST) += glob_kunit.o
 obj-$(CONFIG_HASHTABLE_KUNIT_TEST) += hashtable_test.o
 obj-$(CONFIG_HASH_KUNIT_TEST) += test_hash.o
 obj-$(CONFIG_TEST_IOV_ITER) += kunit_iov_iter.o
+obj-$(CONFIG_TEST_BVECQ) += kunit_bvecq.o
 obj-$(CONFIG_IS_SIGNED_TYPE_KUNIT_TEST) += is_signed_type_kunit.o
 obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
 obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
diff --git a/lib/tests/kunit_bvecq.c b/lib/tests/kunit_bvecq.c
new file mode 100644
index 000000000000..7cdd455df692
--- /dev/null
+++ b/lib/tests/kunit_bvecq.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* bvecq iterator tests.  Exercises:
+ *	- advance
+ *	- revert
+ *	- npages
+ *	- alignment
+ *
+ * See also kunit_iov_iter.c for bvecq copy and extract tests.
+ *
+ * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/uio.h>
+#include <linux/bvecq.h>
+#include <kunit/test.h>
+
+MODULE_DESCRIPTION("bvecq testing");
+MODULE_AUTHOR("David Howells <dhowells@redhat.com>");
+MODULE_LICENSE("GPL");
+
+/*
+ * Info for the fill pattern generator.
+ */
+static const u8 bvecq_kunit_orders[16] = {
+	0, 1, 2, 3,
+	0, 1, 3, 2,
+	0, 2, 1, 3,
+	2, 0, 3, 1
+};
+
+static const s8 bvecq_kunit_fills[16] = {
+	1, 2, 3, 5, 7, 11, 13, 17,
+	-1, -2, -3, -5, -7, -11, -13, -17
+};
+static struct folio *bvecq_kunit_folios[16];
+
+/*
+ * Info for the fill pattern predictor.
+ */
+struct bvecq_test_pagemap {
+	u32 pos;
+	u32 offset;
+	u8 folio;
+	u8 subpage;
+};
+
+static struct bvecq_test_pagemap bvecq_kunit_pagemap[64];
+static int bvecq_kunit_pagemap_nr;
+
+/*
+ * bvecq assembly recipes.
+ */
+enum bvecq_test_cmd {
+	BVECQ_KUNIT_STOP,	/* Stop loading */
+	BVECQ_KUNIT_ALLOC,	/* Append new bvecq of size count and move to it */
+	BVECQ_KUNIT_LOAD,	/* Load count segments into current bvecq */
+	BVECQ_KUNIT_EMPTY,	/* Add count empty segments into current bvecq */
+} __mode(byte);
+
+struct bvecq_test_recipe {
+	enum bvecq_test_cmd	cmd;
+	u8			count;
+};
+
+#define ALLOC(C) { .cmd = BVECQ_KUNIT_ALLOC, .count = C }
+#define LOAD(C)  { .cmd = BVECQ_KUNIT_LOAD,  .count = C }
+#define EMPTY(C) { .cmd = BVECQ_KUNIT_EMPTY, .count = C }
+#define END	 { .cmd = BVECQ_KUNIT_STOP, }
+
+static const struct bvecq_test_recipe bvecq_test_recipe_0[] = {
+	ALLOC(16), LOAD(16),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_1[] = {
+	ALLOC(8), LOAD(8),
+	ALLOC(8), LOAD(8),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_2[] = {
+	ALLOC(9), LOAD(8), EMPTY(1),
+	ALLOC(8), LOAD(8),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_3[] = {
+	ALLOC(9), EMPTY(1), LOAD(2), EMPTY(2), LOAD(2), EMPTY(1),
+	ALLOC(8), LOAD(4), EMPTY(2),
+	ALLOC(8), EMPTY(1), LOAD(4),
+	ALLOC(8), EMPTY(2), LOAD(4), EMPTY(1),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_4[] = {
+	ALLOC(9), LOAD(2), EMPTY(1),
+	ALLOC(2),
+	ALLOC(2), LOAD(2),
+	ALLOC(2), EMPTY(2),
+	ALLOC(8), LOAD(8),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_5[] = {
+	ALLOC(2),
+	ALLOC(9), LOAD(2), EMPTY(1),
+	ALLOC(2),
+	ALLOC(2), LOAD(2),
+	ALLOC(2), EMPTY(2),
+	ALLOC(8), LOAD(8),
+	ALLOC(2),
+	END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_6[] = {
+	ALLOC(2), EMPTY(1),
+	ALLOC(2),
+	ALLOC(9), LOAD(2), EMPTY(1),
+	ALLOC(2),
+	ALLOC(2), LOAD(2),
+	ALLOC(2), EMPTY(2),
+	ALLOC(8), LOAD(8),
+	ALLOC(2),
+	ALLOC(2), EMPTY(1),
+	END
+};
+
+/*
+ * Sets of variations of how much of each folio is added to the bvecq chain,
+ * allowing a part of the folio to be excluded from each end.
+ */
+struct bvecq_test_partials {
+	u16	add_start;	/* Amount to exclude from the low end */
+	u16	sub_end;	/* Amount to exclude from the high end */
+};
+
+/* All whole folios. */
+static const struct bvecq_test_partials bvecq_test_partials_full[16] = {
+	[0 ... 15] = { 0, 0 }
+};
+
+/* Scattered partial folios, with some folios contiguous. */
+static const struct bvecq_test_partials bvecq_test_partials_scattered[16] = {
+	//0, 1, 2, 3,
+	{ 0x277,  0x136 },
+	{ 0x999,  0x1011 },
+	{ 0x2020, 0x0 },
+	{ 0x0,    0x1123 },
+	//0, 1, 3, 2,
+	{ 0x0,    0x136 },
+	{ 0x27,   0x777 },
+	{ 0x771,  0x0 },
+	{ 0x555,  0x0 },
+	//0, 2, 1, 3,
+	{ 0x277,  0x136 },
+	{ 0x1123, 0x0 },
+	{ 0x0,	  0x0 },
+	{ 0,      0x5011 },
+	//2, 0, 3, 1
+	{ 0x2020, 0x0 },
+	{ 0x277,  0x136 },
+	{ 0x1999, 0x1011 },
+	{ 0x0,    0x1010 },
+};
+
+/* Partial folios, all with a byte excluded from each end. */
+static const struct bvecq_test_partials bvecq_test_partials_edges[16] = {
+	[0 ... 15] = { 1, 1 }
+};
+
+/*
+ * Clean up routines.
+ */
+static void bvecq_kunit_release_folios(void *data)
+{
+	struct folio **folios = data;
+
+	release_pages(folios, ARRAY_SIZE(bvecq_kunit_folios));
+	memset(bvecq_kunit_folios, 0, sizeof(bvecq_kunit_folios));
+}
+
+static void bvecq_kunit_bvecq_put(void *data)
+{
+	bvecq_put(data);
+}
+
+/*
+ * Calculate the fill pattern, for a particular byte of a particular subpage of
+ * a particular folio.
+ */
+static inline u8 bvecq_kunit_pattern(unsigned int folio, int subpage, int offset)
+{
+	int fill_step = bvecq_kunit_fills[folio];
+
+	return (subpage + offset * fill_step) & 0xff;
+}
+
+/*
+ * Calculate the fill pattern, given the 'byte position' in the bvecq chain.
+ * This looks up the folio, subpage and offset from a table generated whilst
+ * the bvecq is being assembled.
+ */
+static inline u8 bvecq_kunit_predict(struct kunit *test, size_t pos)
+{
+	size_t last_pos = 0;
+
+	for (int i = 0; i < bvecq_kunit_pagemap_nr; i++) {
+		if (pos < bvecq_kunit_pagemap[i].pos)
+			return bvecq_kunit_pattern(
+				bvecq_kunit_pagemap[i].folio,
+				bvecq_kunit_pagemap[i].subpage,
+				bvecq_kunit_pagemap[i].offset + (pos - last_pos));
+		last_pos = bvecq_kunit_pagemap[i].pos;
+	}
+
+	KUNIT_FAIL(test, "Overran pagemap pos=%zx\n", pos);
+	return 0;
+}
+
+/*
+ * Determine which page we're looking at, taking all the folios to be split
+ * into pages.  This can be used to determine what iov_iter_npages() will say.
+ */
+static inline int bvecq_kunit_pos2page(struct kunit *test, size_t pos)
+{
+	for (int i = 0; i < bvecq_kunit_pagemap_nr; i++)
+		if (pos < bvecq_kunit_pagemap[i].pos)
+			return i;
+
+	if (pos > bvecq_kunit_pagemap[bvecq_kunit_pagemap_nr - 1].pos)
+		KUNIT_FAIL(test, "Overran pagemap pos=%zx\n", pos);
+	return bvecq_kunit_pagemap_nr;
+}
+
+/*
+ * Allocate a variety of folios of different orders and fill them with the
+ * pattern set for each folio.
+ */
+static void __init iov_bvecq_alloc_folios(struct kunit *test)
+{
+	struct folio *folio, **folios = bvecq_kunit_folios;
+	const int nr = ARRAY_SIZE(bvecq_kunit_folios);
+
+	bvecq_kunit_pagemap_nr = 0;
+	memset(bvecq_kunit_pagemap, 0, sizeof(bvecq_kunit_pagemap));
+	memset(bvecq_kunit_folios, 0, sizeof(bvecq_kunit_folios));
+
+	for (int i = 0; i < nr; i++) {
+		folio = folio_alloc(GFP_KERNEL, bvecq_kunit_orders[i]);
+		KUNIT_ASSERT_NOT_NULL(test, folio);
+		folios[i] = folio;
+
+		/* Fill each subpage with a different but predictable pattern. */
+		for (int j = 0; j < folio_nr_pages(folio); j++) {
+			u8 *p = kmap_local_folio(folio, PAGE_SIZE * j);
+
+			for (int k = 0; k < PAGE_SIZE; k++)
+				p[k] = bvecq_kunit_pattern(i, j, k);
+			kunmap_local(p);
+		}
+	}
+
+	kunit_add_action_or_reset(test, bvecq_kunit_release_folios, folios);
+}
+
+/*
+ * Follow the specified recipe to allocate a bvecq chain and stick the folios
+ * and empty gaps into it.  Further, the offset into and length of each folio
+ * is adjusted from the partials list.  Each ALLOC command will append a new
+ * bvecq to the chain and move to filling that.
+ */
+static struct bvecq *__init bvecq_kunit_load_chain(
+	struct kunit *test,
+	const struct bvecq_test_recipe *rec,
+	const struct bvecq_test_partials *parts,
+	size_t *size)
+{
+	struct bvecq *head = NULL, *bq = NULL, *p;
+	size_t pos = 0;
+	int folio_index = 0;
+
+	*size = 0;
+
+	for (; rec->cmd != BVECQ_KUNIT_STOP; rec++) {
+		if (!head)
+			KUNIT_ASSERT_EQ(test, rec->cmd, BVECQ_KUNIT_ALLOC);
+
+		switch (rec->cmd) {
+		case BVECQ_KUNIT_STOP:
+			break;
+
+		case BVECQ_KUNIT_ALLOC:
+			p = bvecq_alloc_one(rec->count, GFP_KERNEL, false);
+			KUNIT_ASSERT_NOT_NULL(test, p);
+
+			if (!head) {
+				head = p;
+				kunit_add_action_or_reset(test, bvecq_kunit_bvecq_put, head);
+				bq = p;
+			} else {
+				bvecq_append(bq, p);
+				bq = p;
+			}
+			break;
+
+		case BVECQ_KUNIT_LOAD:
+			KUNIT_ASSERT_LE(test, folio_index + rec->count,
+					ARRAY_SIZE(bvecq_kunit_orders));
+			KUNIT_ASSERT_LE(test, bq->nr_slots + rec->count, bq->max_slots);
+			for (int i = 0; i < rec->count; i++) {
+				struct folio *folio = bvecq_kunit_folios[folio_index];
+				size_t flen = PAGE_SIZE << bvecq_kunit_orders[folio_index];
+				size_t foff = 0;
+
+				KUNIT_ASSERT_LE(test,
+						parts[folio_index].add_start +
+						parts[folio_index].sub_end,
+						flen);
+
+				foff += parts[folio_index].add_start;
+				flen -= parts[folio_index].add_start;
+				flen -= parts[folio_index].sub_end;
+
+				bvec_set_page(&bq->bv[bq->nr_slots + i],
+					      folio_page(folio, 0), flen, foff);
+				*size += flen;
+
+				for (int j = 0; j < folio_nr_pages(folio); j++) {
+					struct bvecq_test_pagemap *pm;
+					size_t part;
+
+					if (foff >= PAGE_SIZE) {
+						foff -= PAGE_SIZE;
+						continue;
+					}
+					part = min(flen, PAGE_SIZE - foff);
+					pos += part;
+					pm = &bvecq_kunit_pagemap[bvecq_kunit_pagemap_nr];
+					pm->pos     = pos;
+					pm->folio   = folio_index;
+					pm->subpage = j;
+					pm->offset  = foff;
+					bvecq_kunit_pagemap_nr++;
+					flen -= part;
+					if (!flen)
+						break;
+					foff = 0;
+				}
+
+				folio_index++;
+			}
+			bvecq_filled_to(bq, bq->nr_slots + rec->count);
+			break;
+
+		case BVECQ_KUNIT_EMPTY:
+			KUNIT_ASSERT_LE(test, bq->nr_slots + rec->count, bq->max_slots);
+			for (int i = 0; i < rec->count; i++) {
+				bq->bv[bq->nr_slots + i].bv_page = ERR_PTR(-EIO);
+				bq->bv[bq->nr_slots + i].bv_offset = 0;
+				bq->bv[bq->nr_slots + i].bv_len = 0;
+			}
+			bvecq_filled_to(bq, bq->nr_slots + rec->count);
+			break;
+		}
+	}
+
+	return head;
+}
+
+/*
+ * Check the next few bytes of pattern from an iterator.  Note that it is
+ * permitted for the iterator to have less data remaining than the number to
+ * check - in which case the copy is required to come up short.
+ */
+static void __init bvecq_kunit_check_next(struct kunit *test, struct iov_iter *iter,
+					  size_t *pos, size_t *left, size_t nr)
+{
+	size_t part, n;
+	u8 buf[3], pred[3];
+
+	KUNIT_ASSERT_EQ(test, iov_iter_count(iter), *left);
+
+	part = min(*left, nr);
+	n = copy_from_iter(buf, nr, iter);
+	KUNIT_ASSERT_EQ(test, n, part);
+	*left -= n;
+
+	for (size_t i = 0; i < n; i++)
+		pred[i] = bvecq_kunit_predict(test, *pos + i);
+
+	for (size_t i = 0; i < n; i++) {
+		if (buf[i] != pred[i]) {
+			pr_notice(">>> pos=%05zx+%zx c=%zx buf=%*phN pred=%*phN\n",
+				  *pos, i, iov_iter_count(iter) + n,
+				  (int)n, buf, (int)n, pred);
+		}
+		KUNIT_ASSERT_EQ(test, buf[i], pred[i]);
+	}
+
+	*pos += n;
+}
+
+/*
+ * Test advancing and reverting over a bvecq chain.
+ *
+ * The loop starts at each 'byte position' in the chain, advancing a small
+ * amount, copying a few bytes of data, checking the fill pattern, reverting a
+ * bit, checking, reverting, checking and then reverting to a byte position one
+ * further on than the previous loop.  Use small prime numbers for the advance
+ * and first revert so that we should test crossing the edges of the folios.
+ */
+static void __init bvecq_kunit_advance(struct kunit *test,
+				       const struct bvecq_test_recipe *recipe,
+				       const struct bvecq_test_partials *parts)
+{
+	struct iov_iter iter;
+	struct bvecq *bq, *p;
+	unsigned long bvec_align = 0;
+	size_t size, pos = 0, from;
+	int npages;
+
+	iov_bvecq_alloc_folios(test);
+	bq = bvecq_kunit_load_chain(test, recipe, parts, &size);
+
+	/* Compound up the alignments for ITER_BVEC on each individual bvecq
+	 * span and compare that to the alignment for ITER_BVECQ.  There is an
+	 * issue in ITER_BVEC, however, in that it includes bv_offset from
+	 * bio_vecs for which bv_len is 0, which ITER_BVECQ does not.
+	 */
+	for (p = bq; p; p = p->next) {
+		size_t psize = 0;
+
+		for (int i = 0; i < p->nr_slots; i++)
+			psize += p->bv[i].bv_len;
+
+		if (!psize)
+			continue;
+		iov_iter_bvec(&iter, ITER_SOURCE, p->bv, p->nr_slots, psize);
+		bvec_align |= iov_iter_alignment(&iter);
+	}
+
+	iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, size);
+
+	KUNIT_ASSERT_EQ(test, iov_iter_alignment(&iter), bvec_align);
+
+	npages = iov_iter_npages(&iter, INT_MAX);
+	from = iov_iter_count(&iter);
+	do {
+		size_t left = from, part;
+
+		KUNIT_ASSERT_EQ(test, iov_iter_npages(&iter, INT_MAX),
+				npages - bvecq_kunit_pos2page(test, pos));
+
+		/* 3 steps forward. */
+		part = min(left, 3);
+		iov_iter_advance(&iter, 3); /* May be short. */
+		pos += part;
+		left -= part;
+		bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+		/* 2 steps back */
+		part = umin(2, left);
+		iov_iter_revert(&iter, part);
+		pos -= part;
+		left += part;
+		bvecq_kunit_check_next(test, &iter, &pos, &left, 2);
+
+		/* 1 more step back */
+		part = umin(1, left);
+		iov_iter_revert(&iter, part);
+		pos -= part;
+		left += part;
+		bvecq_kunit_check_next(test, &iter, &pos, &left, 1);
+
+		KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+		/* Move to one place on from the current loop. */
+		from--;
+		if (iov_iter_count(&iter) < from) {
+			part = from - iov_iter_count(&iter);
+			iov_iter_revert(&iter, part);
+			pos -= part;
+		}
+		KUNIT_ASSERT_EQ(test, from, iov_iter_count(&iter));
+	} while (from > 0);
+}
+
+/*
+ * Test jumping around in a bvecq chain using advance and revert.
+ *
+ * The loop starts at the start of the chain, then jumps forwards through the
+ * chain and then back again by large amounts, sampling the data each time.
+ */
+static void __init bvecq_kunit_jump(struct kunit *test,
+				    const struct bvecq_test_recipe *recipe,
+				    const struct bvecq_test_partials *parts)
+{
+	struct iov_iter iter;
+	struct bvecq *bq;
+	size_t size, pos = 0, step = 0x100;
+	bool dir = false;
+	int npages;
+
+	iov_bvecq_alloc_folios(test);
+	bq = bvecq_kunit_load_chain(test, recipe, parts, &size);
+
+	iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, size);
+
+	npages = iov_iter_npages(&iter, INT_MAX);
+	while (step < size) {
+		size_t left = iov_iter_count(&iter), part;
+
+		KUNIT_ASSERT_EQ(test, iov_iter_npages(&iter, INT_MAX),
+				npages - bvecq_kunit_pos2page(test, pos));
+
+		if (!dir) {
+			/* Advance by step and then 3 more in check. */
+			part = min(left, step);
+			iov_iter_advance(&iter, step); /* May be short. */
+			pos += part;
+			left -= part;
+			bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+			KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+			if (left == 0) {
+				/* Switch to reverting. */
+				dir = true;
+				step += 3;
+			}
+		} else {
+			/* Revert by step and then advance by 3 in check. */
+			part = min(size - left, step);
+			iov_iter_revert(&iter, part);
+			pos -= part;
+			left += part;
+			bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+			KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+			if (pos <= 3) {
+				/* Switch to advancing. */
+				dir = false;
+				step *= 2;
+			}
+		}
+	}
+}
+
+/*
+ * Run the individual test combinations
+ */
+#define ADVANCE_TESTS_SET(CHECK, PARTIALS) \
+	TEST(CHECK, PARTIALS, 0)	\
+	TEST(CHECK, PARTIALS, 1)	\
+	TEST(CHECK, PARTIALS, 2)	\
+	TEST(CHECK, PARTIALS, 3)	\
+	TEST(CHECK, PARTIALS, 4)	\
+	TEST(CHECK, PARTIALS, 5)	\
+	TEST(CHECK, PARTIALS, 6)
+
+#define ADVANCE_TESTS(CHECK)			\
+	ADVANCE_TESTS_SET(CHECK, full)		\
+	ADVANCE_TESTS_SET(CHECK, scattered)	\
+	ADVANCE_TESTS_SET(CHECK, edges)
+
+#define TEST(CHECK, PARTIALS, RECIPE) \
+static void __init bvecq_kunit_##CHECK##_##PARTIALS##_##RECIPE(struct kunit *test) \
+{ \
+	bvecq_kunit_##CHECK(test, \
+			    bvecq_test_recipe_##RECIPE,	      \
+			    bvecq_test_partials_##PARTIALS);  \
+}
+
+/* Emit test stub functions. */
+ADVANCE_TESTS(advance);
+ADVANCE_TESTS(jump);
+#undef TEST
+
+#define TEST(CHECK, PARTIALS, RECIPE)				\
+	KUNIT_CASE(bvecq_kunit_##CHECK##_##PARTIALS##_##RECIPE),
+
+static struct kunit_case __refdata bvecq_kunit_cases[] = {
+	ADVANCE_TESTS(advance)
+	ADVANCE_TESTS(jump)
+	{}
+};
+
+static struct kunit_suite bvecq_kunit_suite = {
+	.name = "bvecq",
+	.test_cases = bvecq_kunit_cases,
+};
+
+kunit_test_suites(&bvecq_kunit_suite);


  parent reply	other threads:[~2026-11-07 16:09 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07 16:08 [PATCH v16 00/15] netfs, iov_iter: Use a chain of bio_vec arrays instead of folio_queue David Howells
2026-11-07 16:08 ` [PATCH v16 01/15] Add a function to kmap one page of a multipage bio_vec David Howells
2026-11-07 16:08 ` [PATCH v16 02/15] iov_iter: Add a segmented queue of bio_vec[] David Howells
2026-11-07 16:08 ` [PATCH v16 03/15] netfs: Add some tools for managing bvecq chains David Howells
2026-11-07 16:08 ` [PATCH v16 04/15] iov_iter: Add an API document for the bvecq structure David Howells
2026-11-07 16:08 ` David Howells [this message]
2026-11-07 16:08 ` [PATCH v16 06/15] afs: Fix an inconsistency in the symlink code David Howells
2026-11-07 16:08 ` [PATCH v16 07/15] netfs: Explicitly pass tail padding to netfs_writeback_single() David Howells
2026-11-07 16:08 ` [PATCH v16 08/15] netfs: Change netfs_writeback_single() to not look inside the iterator David Howells
2026-11-07 16:08 ` [PATCH v16 09/15] afs: Use a bvecq to hold dir content rather than folioq David Howells
2026-11-07 16:08 ` [PATCH v16 10/15] cifs: Use a bvecq for buffering instead of a folioq David Howells
2026-11-07 16:08 ` [PATCH v16 11/15] smbdirect: Support ITER_BVECQ in smbdirect_map_sges_from_iter() David Howells
2026-11-07 16:08 ` [PATCH v16 12/15] netfs: Switch folioq to bvecq David Howells
2026-11-07 16:08 ` [PATCH v16 13/15] smbdirect: Remove support for ITER_FOLIOQ from smbdirect_map_sges_from_iter() David Howells
2026-11-07 16:08 ` [PATCH v16 14/15] iov_iter: Remove ITER_FOLIOQ David Howells
2026-11-07 16:08 ` [PATCH v16 15/15] netfs: Remove folio_queue David Howells

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261107160823.1465553-6-dhowells@redhat.com \
    --to=dhowells@redhat.com \
    --cc=asmadeus@codewreck.org \
    --cc=axboe@kernel.dk \
    --cc=ceph-devel@vger.kernel.org \
    --cc=christian@brauner.io \
    --cc=ericvh@kernel.org \
    --cc=hch@infradead.org \
    --cc=idryomov@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-afs@lists.infradead.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=marc.dionne@auristor.com \
    --cc=metze@samba.org \
    --cc=netfs@lists.linux.dev \
    --cc=pc@manguebit.org \
    --cc=v9fs@lists.linux.dev \
    --cc=willy@infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®