[PATCH netfs v1 2/2] netfs: add KUnit coverage for reissue prepare_write errno

From: Iván Ezequiel Rodriguez

Date: Wed Oct 28 2026 - 14:19:42 EST


Stub netfs_write_subrequest_terminated() and call netfs_reissue_write()
with FAILED + -EIO. Unpatched trees terminate with 0; patched keep -EIO.

./tools/testing/kunit/kunit.py run --kunitconfig=fs/netfs/.kunitconfig \
netfs_reissue_write*

Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@xxxxxxxxx>
---
fs/netfs/.kunitconfig | 3 ++
fs/netfs/Kconfig | 9 +++++
fs/netfs/Makefile | 2 +
fs/netfs/write_collect.c | 4 ++
fs/netfs/write_reissue_kunit.c | 73 ++++++++++++++++++++++++++++++++++
5 files changed, 91 insertions(+)
create mode 100644 fs/netfs/.kunitconfig
create mode 100644 fs/netfs/write_reissue_kunit.c

diff --git a/fs/netfs/.kunitconfig b/fs/netfs/.kunitconfig
new file mode 100644
index 000000000000..c783d180ab8f
--- /dev/null
+++ b/fs/netfs/.kunitconfig
@@ -0,0 +1,3 @@
+CONFIG_KUNIT=y
+CONFIG_NETFS_SUPPORT=y
+CONFIG_NETFS_KUNIT_TESTS=y
diff --git a/fs/netfs/Kconfig b/fs/netfs/Kconfig
index d0e7b0971fa3..d796dbe4bd7c 100644
--- a/fs/netfs/Kconfig
+++ b/fs/netfs/Kconfig
@@ -33,6 +33,15 @@ config NETFS_DEBUG
management module. If this is set, the debugging output may be
enabled by setting bits in /sys/module/netfs/parameters/debug.

+config NETFS_KUNIT_TESTS
+ tristate "KUnit tests for netfs" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ select NETFS_SUPPORT
+ help
+ This builds netfs KUnit tests (e.g. write reissue errno).
+ Say N unless developing netfs.
+
config FSCACHE
bool "General filesystem local caching manager"
depends on NETFS_SUPPORT
diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile
index 54834cde7e56..fa1544b9a199 100644
--- a/fs/netfs/Makefile
+++ b/fs/netfs/Makefile
@@ -33,4 +33,6 @@ netfs-$(CONFIG_FSCACHE) += fscache_proc.o
endif
netfs-$(CONFIG_FSCACHE_STATS) += fscache_stats.o

+netfs-$(CONFIG_NETFS_KUNIT_TESTS) += write_reissue_kunit.o
+
obj-$(CONFIG_NETFS_SUPPORT) += netfs.o
diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c
index 6e8ea534230d..f3236fe494bf 100644
--- a/fs/netfs/write_collect.c
+++ b/fs/netfs/write_collect.c
@@ -11,6 +11,7 @@
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
+#include <kunit/static_stub.h>
#include "internal.h"

/* Notes made in the collector */
@@ -529,6 +530,9 @@ void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error)
struct netfs_io_subrequest *subreq = _op;
struct netfs_io_request *wreq = subreq->rreq;

+ KUNIT_STATIC_STUB_REDIRECT(netfs_write_subrequest_terminated,
+ _op, transferred_or_error);
+
_enter("%x[%x] %zd", wreq->debug_id, subreq->debug_index, transferred_or_error);

switch (subreq->source) {
diff --git a/fs/netfs/write_reissue_kunit.c b/fs/netfs/write_reissue_kunit.c
new file mode 100644
index 000000000000..fca5f7dd9265
--- /dev/null
+++ b/fs/netfs/write_reissue_kunit.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * netfs_reissue_write must not clear prepare_write() errno.
+ */
+#include <kunit/test.h>
+#include <kunit/static_stub.h>
+#include <linux/uio.h>
+#include "internal.h"
+
+struct term_capture {
+ ssize_t transferred_or_error;
+ bool called;
+};
+
+static void capture_terminated(void *_op, ssize_t transferred_or_error)
+{
+ struct netfs_io_subrequest *subreq = _op;
+ struct term_capture *cap = kunit_get_current_test()->priv;
+
+ cap->called = true;
+ cap->transferred_or_error = transferred_or_error;
+ /* Mirror production: leave subreq->error as prepare_write set it. */
+ (void)subreq;
+}
+
+/*
+ * prepare_write_failed() sets FAILED + errno, then write_retry still calls
+ * reissue_write(). Before the fix, reissue wiped error to 0 so terminated
+ * reported success and mapping_set_error(0) was a no-op.
+ */
+static void reissue_preserves_prep_error(struct kunit *test)
+{
+ struct term_capture cap = {};
+ struct netfs_io_request wreq = {};
+ struct netfs_io_stream stream = {
+ .source = NETFS_UPLOAD_TO_SERVER,
+ };
+ struct netfs_io_subrequest subreq = {
+ .rreq = &wreq,
+ .len = 1,
+ .transferred = 0,
+ .error = -EIO,
+ .source = NETFS_UPLOAD_TO_SERVER,
+ };
+ char buf[1] = { 0 };
+ struct kvec kvec = { .iov_base = buf, .iov_len = 1 };
+ struct iov_iter source;
+
+ __set_bit(NETFS_SREQ_FAILED, &subreq.flags);
+ iov_iter_kvec(&source, ITER_SOURCE, &kvec, 1, 1);
+
+ test->priv = &cap;
+ kunit_activate_static_stub(test, netfs_write_subrequest_terminated,
+ capture_terminated);
+
+ netfs_reissue_write(&stream, &subreq, &source);
+
+ KUNIT_EXPECT_TRUE(test, cap.called);
+ KUNIT_EXPECT_EQ(test, cap.transferred_or_error, -EIO);
+ KUNIT_EXPECT_EQ(test, subreq.error, -EIO);
+}
+
+static struct kunit_case netfs_reissue_test_cases[] = {
+ KUNIT_CASE(reissue_preserves_prep_error),
+ {}
+};
+
+static struct kunit_suite netfs_reissue_test_suite = {
+ .name = "netfs_reissue_write",
+ .test_cases = netfs_reissue_test_cases,
+};
+
+kunit_test_suite(netfs_reissue_test_suite);
--
2.43.0