kunit: Test iSCSI transmit progress and completion

Integrate iSCSI test objects in out-of-tree module builds.
Move transmit state names to a private header and link a separate
suite with task-scoped socket and completion boundaries.

Exercise data, padding and digest progress, retries, short sends and
TX_END completion. Preserve socket arguments and completion order
without creating a socket or initiator session.
This commit is contained in:
Gleb Chesnokov
2026-10-14 15:08:03 +03:00
parent d41d195c4f
commit 714bfca753
11 changed files with 326 additions and 21 deletions
+1
View File
@@ -63,6 +63,7 @@ validates the selection against the supplied `.ko` files.
| `scst.ko` | `ALUA CDB CM EVENT LIB LIFECYCLE ORDER UNMAP` |
| `scst_vdisk.ko` | `VDISK` |
| `scst_user.ko` | `USER` |
| `iscsi-scst.ko` | `ISCSI` |
The runner reads suite metadata and dependencies from the modules and loads selected
owners in dependency order. Supply all required SCST modules from the same build;
+4 -2
View File
@@ -54,8 +54,10 @@ artifacts. Change their sources or recipes instead.
The focused compile entry point is top-level `make iscsi` or `make -C
iscsi-scst`; it builds both user and kernel parts and may run compatibility
conftests. There is no component unit-test target; use repository CI,
checkpatch, and regression entry points as appropriate.
conftests. Use repository CI, checkpatch, and regression entry points as
appropriate. Before changing tests under `kernel/kunit/`, read
[the KUnit guide](../doc/KUNIT.md) for build and execution entry points,
private interfaces and fixture cleanup.
Do not install or run the daemon, open portals, apply iSCSI configuration,
create targets/sessions/connections, perform discovery/login/logout, manipulate
+10 -2
View File
@@ -138,7 +138,7 @@ CONFTEST_CFLAGS = \
ISER_CFLAGS = $(OFED_CFLAGS) -DOFED_FLAVOR=$(OFED_FLAVOR) $(CONFTEST_CFLAGS)
mods: include/iscsi_scst_itf_ver.h $(CONFTEST_OUTPUTS)
$(MAKE) -C $(KDIR) M=$(KMOD) CONFTEST_CFLAGS="$(CONFTEST_CFLAGS)"\
$(MAKE) -C $(KDIR) M=$(KMOD) $(SCST_KUNIT_FLAGS) CONFTEST_CFLAGS="$(CONFTEST_CFLAGS)"\
$(shell [ -n "$(PASS_CC_TO_MAKE)" ] && echo CC="$(CC)") \
modules
echo "$@: INFINIBAND_ENABLED = $(INFINIBAND_ENABLED)"
@@ -149,6 +149,12 @@ mods: include/iscsi_scst_itf_ver.h $(CONFTEST_OUTPUTS)
$(OFED_CONFIG) modules; \
fi
.PHONY: kunit-build
kunit-build: include/iscsi_scst_itf_ver.h conftest/sock_recvmsg/result-$(KVER).txt
$(MAKE) -C $(KDIR) M=$(KMOD) $(SCST_KUNIT_FLAGS) \
CONFTEST_CFLAGS="$(CONFTEST_CFLAGS)" \
$(shell [ -n "$(PASS_CC_TO_MAKE)" ] && echo CC="$(CC)") iscsi-scst.ko
progs: include/iscsi_scst_itf_ver.h
$(MAKE) -C usr
@@ -166,7 +172,7 @@ install: all
@install -vD -m 755 usr/iscsi-scst-adm $(DESTDIR)$(SBINDIR)/iscsi-scst-adm
@install -vD -m 644 doc/manpages/iscsi-scst-adm.8 $(DESTDIR)$(MANDIR)/man8/iscsi-scst-adm.8
(cd $(KMOD) && KDIR=$(KDIR) ../../scripts/sign-modules)
$(MAKE) -C $(KDIR) M=$(KMOD) \
$(MAKE) -C $(KDIR) M=$(KMOD) $(SCST_KUNIT_FLAGS) \
$(shell [ -n "$(PASS_CC_TO_MAKE)" ] && echo CC="$(CC)") \
INSTALL_MOD_DIR=$(INSTALL_MOD_DIR) \
$$([ -n "$(DEPMOD)" ] && echo "DEPMOD=$(DEPMOD)") \
@@ -333,3 +339,5 @@ release-archive:
../scripts/generate-release-archive iscsi-scst "$$(sed -n 's/^#define[[:blank:]]ISCSI_VERSION_STRING[[:blank:]]*\"\([^\"]*\)\".*/\1/p' include/iscsi_scst_ver.h)"
.PHONY: all mods progs install clean extraclean
include ../scst/src/kunit/select.mk
+3
View File
@@ -39,3 +39,6 @@ obj-m += iscsi-scst.o
iscsi-scst-objs := iscsi.o nthread.o config.o digest.o \
conn.o session.o target.o event.o param.o \
iscsit_transport.o
scst-kunit-module := iscsi-scst
include $(src)/../../scst/src/kunit/Makefile
@@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifdef INSIDE_KERNEL_TREE
#include "../../../kunit/scst_kunit.h"
#else
#include "../../../../scst/src/kunit/scst_kunit.h"
#endif
#include "../../iscsi_trace_flag.h"
#include "../../iscsi.h"
#include "../../nthread.h"
#include "../../nthread_test.h"
struct iscsi_tx_fixture {
struct iscsi_conn conn;
struct iscsi_session session;
struct iscsi_cmnd rsp;
struct iscsit_transport transport;
struct socket socket;
int send_result;
unsigned int sends;
unsigned int closes;
unsigned int completions;
};
static int iscsi_tx_write_stub(struct iscsi_conn *conn)
{
struct kunit *test = kunit_get_current_test();
struct iscsi_tx_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, conn, &f->conn);
f->sends++;
if (f->send_result > 0)
conn->write_size -= f->send_result;
return f->send_result;
}
static int iscsi_tx_send_stub(struct socket *sock, struct msghdr *msg,
struct kvec *iov, size_t size)
{
struct kunit *test = kunit_get_current_test();
struct iscsi_tx_fixture *f = test->priv;
unsigned int i;
KUNIT_EXPECT_PTR_EQ(test, sock, &f->socket);
KUNIT_EXPECT_EQ(test, msg->msg_flags, MSG_NOSIGNAL | MSG_DONTWAIT);
KUNIT_EXPECT_EQ(test, size, (size_t)f->conn.write_size);
KUNIT_EXPECT_EQ(test, iov->iov_len, size);
if (f->conn.write_state == TX_PADDING) {
for (i = 0; i < size; i++)
KUNIT_EXPECT_EQ(test, ((u8 *)iov->iov_base)[i], (u8)0);
} else {
KUNIT_EXPECT_EQ(test, f->conn.write_state, TX_DDIGEST);
KUNIT_EXPECT_PTR_EQ(test, iov->iov_base,
(u8 *)&f->rsp.ddigest + sizeof(u32) - size);
}
f->sends++;
return f->send_result;
}
static void iscsi_tx_close_stub(struct iscsi_conn *conn, int flags)
{
struct kunit *test = kunit_get_current_test();
struct iscsi_tx_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, conn, &f->conn);
KUNIT_EXPECT_EQ(test, flags, ISCSI_CONN_ACTIVE_CLOSE);
KUNIT_EXPECT_EQ(test, conn->write_state, TX_END);
KUNIT_EXPECT_EQ(test, conn->write_size, 0);
KUNIT_EXPECT_EQ(test, f->completions, 0U);
conn->closing = 1;
f->closes++;
}
static void iscsi_tx_complete_stub(struct iscsi_cmnd *cmnd)
{
struct kunit *test = kunit_get_current_test();
struct iscsi_tx_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, cmnd, &f->rsp);
KUNIT_EXPECT_EQ(test, f->conn.write_state, TX_END);
KUNIT_EXPECT_EQ(test, f->conn.write_size, 0);
KUNIT_EXPECT_PTR_EQ(test, f->conn.write_cmnd, cmnd);
f->completions++;
}
static int iscsi_tx_test_init(struct kunit *test)
{
struct iscsi_tx_fixture *f;
f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL);
if (!f)
return -ENOMEM;
test->priv = f;
f->conn.session = &f->session;
f->session.initiator_name = "kunit-private-initiator";
f->conn.transport = &f->transport;
f->transport.iscsit_mark_conn_closed = iscsi_tx_close_stub;
f->conn.sock = &f->socket;
f->conn.write_cmnd = &f->rsp;
f->conn.ddigest_type = DIGEST_NONE;
f->conn.wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
#ifdef CONFIG_SCST_EXTRACHECKS
f->conn.wr_task = current;
#endif
f->rsp.conn = &f->conn;
f->rsp.ddigest = 0x12345678;
kunit_activate_static_stub(test, write_data, iscsi_tx_write_stub);
kunit_activate_static_stub(test, iscsi_tx_sendmsg, iscsi_tx_send_stub);
kunit_activate_static_stub(test, iscsi_tx_complete, iscsi_tx_complete_stub);
return 0;
}
struct iscsi_tx_result_case {
const char *name;
int state;
int result;
};
#define TX_RESULTS(phase, state) \
{ phase "-closed", state, 0 }, \
{ phase "-io-error", state, -EIO }, \
{ phase "-broken-pipe", state, -EPIPE }, \
{ phase "-retry", state, -EAGAIN }, \
{ phase "-interrupted", state, -ERESTARTSYS }, \
{ phase "-short-write", state, 1 }, \
{ phase "-complete", state, 3 }
static const struct iscsi_tx_result_case iscsi_tx_results[] = {
TX_RESULTS("data", TX_BHS_DATA),
TX_RESULTS("padding", TX_PADDING),
TX_RESULTS("digest", TX_DDIGEST),
};
#undef TX_RESULTS
static void iscsi_tx_result_desc(const struct iscsi_tx_result_case *p, char *desc)
{
strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(iscsi_tx_result, iscsi_tx_results, iscsi_tx_result_desc);
static void iscsi_tx_result_test(struct kunit *test)
{
const struct iscsi_tx_result_case *p = test->param_value;
struct iscsi_tx_fixture *f = test->priv;
bool fatal = p->result <= 0 && p->result != -EAGAIN && p->result != -ERESTARTSYS;
bool done = fatal || p->result == 3;
f->conn.write_state = p->state;
f->conn.write_size = 3;
f->send_result = p->result;
KUNIT_EXPECT_EQ(test, iscsi_send(&f->conn), p->result);
KUNIT_EXPECT_EQ(test, f->sends, 1U);
KUNIT_EXPECT_EQ(test, f->closes, (unsigned int)fatal);
KUNIT_EXPECT_EQ(test, f->completions, (unsigned int)done);
KUNIT_EXPECT_EQ(test, f->conn.write_state, done ? TX_INIT : p->state);
KUNIT_EXPECT_EQ(test, f->conn.write_size, done ? 0 : (p->result > 0 ? 2 : 3));
if (done)
KUNIT_EXPECT_PTR_EQ(test, f->conn.write_cmnd, NULL);
else
KUNIT_EXPECT_PTR_EQ(test, f->conn.write_cmnd, &f->rsp);
if (!done) {
/* Retry continues the same response, including the digest offset. */
f->send_result = f->conn.write_size;
KUNIT_EXPECT_GT(test, iscsi_send(&f->conn), 0);
KUNIT_EXPECT_EQ(test, f->completions, 1U);
KUNIT_EXPECT_EQ(test, f->closes, 0U);
KUNIT_EXPECT_EQ(test, f->conn.write_state, TX_INIT);
KUNIT_EXPECT_PTR_EQ(test, f->conn.write_cmnd, NULL);
}
}
struct iscsi_tx_padding_case {
const char *name;
unsigned int data_size;
bool digest;
};
static const struct iscsi_tx_padding_case iscsi_tx_paddings[] = {
{ "aligned", 4, false },
{ "one-byte", 3, false },
{ "two-bytes", 2, false },
{ "three-bytes", 1, false },
{ "aligned-digest", 4, true },
{ "one-byte-digest", 3, true },
{ "two-bytes-digest", 2, true },
{ "three-bytes-digest", 1, true },
};
static void iscsi_tx_padding_desc(const struct iscsi_tx_padding_case *p, char *desc)
{
strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(iscsi_tx_padding_case, iscsi_tx_paddings, iscsi_tx_padding_desc);
static void iscsi_tx_padding_test(struct kunit *test)
{
const struct iscsi_tx_padding_case *p = test->param_value;
struct iscsi_tx_fixture *f = test->priv;
unsigned int padding = (4 - p->data_size) & 3;
f->conn.write_state = TX_BHS_DATA;
f->conn.write_size = 1;
f->rsp.pdu.datasize = p->data_size;
f->conn.ddigest_type = p->digest ? DIGEST_CRC32C : DIGEST_NONE;
f->send_result = 1;
KUNIT_EXPECT_EQ(test, iscsi_send(&f->conn), 1);
KUNIT_EXPECT_EQ(test, f->conn.write_state,
padding ? TX_PADDING : (p->digest ? TX_INIT_DDIGEST : TX_INIT));
KUNIT_EXPECT_EQ(test, f->completions, (unsigned int)(!padding && !p->digest));
if (padding) {
f->send_result = -EAGAIN;
KUNIT_EXPECT_EQ(test, iscsi_send(&f->conn), -EAGAIN);
KUNIT_EXPECT_EQ(test, f->conn.write_size, (int)padding);
/* With a digest, the same call also makes a short digest write. */
f->send_result = padding;
KUNIT_EXPECT_EQ(test, iscsi_send(&f->conn), (int)padding);
}
if (p->digest) {
f->send_result = padding ? sizeof(u32) - padding : sizeof(u32);
KUNIT_EXPECT_GT(test, iscsi_send(&f->conn), 0);
}
KUNIT_EXPECT_EQ(test, f->completions, 1U);
KUNIT_EXPECT_EQ(test, f->closes, 0U);
KUNIT_EXPECT_EQ(test, f->conn.write_size, 0);
KUNIT_EXPECT_EQ(test, f->conn.write_state, TX_INIT);
KUNIT_EXPECT_PTR_EQ(test, f->conn.write_cmnd, NULL);
}
static struct kunit_case iscsi_tx_cases[] = {
KUNIT_CASE_PARAM(iscsi_tx_result_test, iscsi_tx_result_gen_params),
KUNIT_CASE_PARAM(iscsi_tx_padding_test, iscsi_tx_padding_case_gen_params),
{}
};
scst_kunit_suite(iscsi_tx_suite, "ISCSI", "-tx",
.init = iscsi_tx_test_init,
.test_cases = iscsi_tx_cases,
);
+25 -16
View File
@@ -28,6 +28,8 @@
#include "iscsi_trace_flag.h"
#include "iscsi.h"
#include "digest.h"
#include "nthread.h"
#include "nthread_test.h"
#undef DEFAULT_SYMBOL_NAMESPACE
#define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE
@@ -49,16 +51,6 @@ enum rx_state {
RX_PADDING,
};
enum tx_state {
TX_INIT = 0, /* Must be zero for better "switch" optimization. */
TX_BHS_DATA,
TX_INIT_PADDING,
TX_PADDING,
TX_INIT_DDIGEST,
TX_DDIGEST,
TX_END,
};
static void free_pending_commands(struct iscsi_conn *conn)
{
struct iscsi_session *session = conn->session;
@@ -1068,7 +1060,8 @@ out:
TRACE_EXIT();
}
static int write_data(struct iscsi_conn *conn)
SCST_KUNIT_VISIBLE
int write_data(struct iscsi_conn *conn)
{
struct socket *sock;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
@@ -1087,6 +1080,8 @@ static int write_data(struct iscsi_conn *conn)
int offset, idx, flags, res = 0;
bool ref_cmd_to_parent;
SCST_KUNIT_REDIRECT(write_data, conn);
TRACE_ENTRY();
write_cmnd = conn->write_cmnd;
@@ -1324,6 +1319,22 @@ static void exit_tx(struct iscsi_conn *conn, int res)
}
}
SCST_KUNIT_VISIBLE
int iscsi_tx_sendmsg(struct socket *sock, struct msghdr *msg,
struct kvec *iov, size_t size)
{
SCST_KUNIT_REDIRECT(iscsi_tx_sendmsg, sock, msg, iov, size);
return kernel_sendmsg(sock, msg, iov, 1, size);
}
SCST_KUNIT_VISIBLE
void iscsi_tx_complete(struct iscsi_cmnd *cmnd)
{
SCST_KUNIT_REDIRECT(iscsi_tx_complete, cmnd);
cmnd_tx_end(cmnd);
rsp_cmnd_release(cmnd);
}
static int tx_ddigest(struct iscsi_cmnd *cmnd, int state)
{
int res, rest = cmnd->conn->write_size;
@@ -1337,7 +1348,7 @@ static int tx_ddigest(struct iscsi_cmnd *cmnd, int state)
iov.iov_base = (char *)(&cmnd->ddigest) + (sizeof(u32) - rest);
iov.iov_len = rest;
res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
res = iscsi_tx_sendmsg(cmnd->conn->sock, &msg, &iov, rest);
if (res > 0) {
cmnd->conn->write_size -= res;
if (!cmnd->conn->write_size)
@@ -1383,7 +1394,7 @@ static int tx_padding(struct iscsi_cmnd *cmnd, int state)
iov.iov_base = (char *)&padding;
iov.iov_len = rest;
res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
res = iscsi_tx_sendmsg(cmnd->conn->sock, &msg, &iov, rest);
if (res > 0) {
cmnd->conn->write_size -= res;
if (!cmnd->conn->write_size)
@@ -1485,9 +1496,7 @@ int iscsi_send(struct iscsi_conn *conn)
res, cmnd_opcode(cmnd), conn->write_size);
sBUG();
}
cmnd_tx_end(cmnd);
rsp_cmnd_release(cmnd);
iscsi_tx_complete(cmnd);
conn->write_cmnd = NULL;
conn->write_state = TX_INIT;
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_ISCSI_NTHREAD_H
#define SCST_ISCSI_NTHREAD_H
enum tx_state {
TX_INIT = 0, /* Must be zero for better "switch" optimization. */
TX_BHS_DATA,
TX_INIT_PADDING,
TX_PADDING,
TX_INIT_DDIGEST,
TX_DDIGEST,
TX_END,
};
#endif /* SCST_ISCSI_NTHREAD_H */
+19
View File
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_ISCSI_NTHREAD_TEST_H
#define SCST_ISCSI_NTHREAD_TEST_H
#ifdef INSIDE_KERNEL_TREE
#include "../scst_test.h"
#else
#include "../../scst/src/scst_test.h"
#endif
#ifdef SCST_KUNIT_ENABLED
int write_data(struct iscsi_conn *conn);
int iscsi_tx_sendmsg(struct socket *sock, struct msghdr *msg,
struct kvec *iov, size_t size);
void iscsi_tx_complete(struct iscsi_cmnd *cmnd);
#endif /* SCST_KUNIT_ENABLED */
#endif /* SCST_ISCSI_NTHREAD_TEST_H */
+1
View File
@@ -25,6 +25,7 @@ OWNERS = {
'scst': ('scst/src', 'scst/kernel/in-tree/Makefile.scst'),
'scst_vdisk': ('scst/src/dev_handlers', 'scst/kernel/in-tree/Makefile.dev_handlers'),
'scst_user': ('scst/src/dev_handlers', 'scst/kernel/in-tree/Makefile.dev_handlers'),
'iscsi-scst': ('iscsi-scst/kernel', 'iscsi-scst/kernel/Makefile.in-kernel'),
}
+4 -1
View File
@@ -1,10 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
# Build components in dependency order and record their test modules.
SCST_KUNIT_COMPONENTS := scst
SCST_KUNIT_COMPONENTS := scst iscsi-scst
SCST_KUNIT_MODULES_scst := \
scst/src/scst.ko \
scst/src/dev_handlers/scst_vdisk.ko \
scst/src/dev_handlers/scst_user.ko
SCST_KUNIT_MODULES_iscsi-scst := \
iscsi-scst/kernel/iscsi-scst.ko
SCST_KUNIT_MODULES = $(foreach component,$(SCST_KUNIT_COMPONENTS),$(SCST_KUNIT_MODULES_$(component)))
+3
View File
@@ -14,6 +14,9 @@ scst-kunit-scst := \
kunit/order/suites/scst_kunit_order.o \
kunit/unmap/suites/scst_kunit_unmap.o
scst-kunit-iscsi-scst := \
kunit/suites/iscsi_tx_test.o
scst-kunit-scst_user := \
kunit/user/suites/scst_user_cleanup_test.o