kunit: Test copy-manager updates and read-only admission

Expose private functions to a separate test object and stub discovery,
registration and default-group lookup. Keep the actual update and
admission paths unchanged.

Check coalescing, failed inquiry, removal before retry and read-only
LUN rejection using private state.
This commit is contained in:
Gleb Chesnokov
2026-10-14 15:08:03 +03:00
parent bc20ee1ca1
commit 593023ba41
6 changed files with 306 additions and 17 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ validates the selection against the supplied `.ko` files.
| Module | Labels |
| --- | --- |
| `scst.ko` | `ALUA CDB EVENT LIB ORDER UNMAP` |
| `scst.ko` | `ALUA CDB CM EVENT LIB ORDER UNMAP` |
The runner reads suite metadata from the modules and loads selected owners
with their dependencies. Unselected suites are not counted as skipped.
+2 -1
View File
@@ -311,7 +311,8 @@ scst_04_main="scst/src/scst_main.c scst/src/scst_priv.h \
scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \
scst/src/scst_event.c scst/src/scst_event_priv.h scst/src/scst_no_dlm.c \
scst/src/scst_test.h \
scst/src/scst_event_test.h"
scst/src/scst_event_test.h \
scst/src/scst_copy_mgr_test.h"
scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \
scst/src/scst_local_cmd.h \
scst/src/scst_targ_test.h"
+243
View File
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "../../scst_kunit.h"
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include "scst.h"
#endif
#include "../../../scst_priv.h"
#include "../../../scst_copy_mgr_test.h"
struct cm_fixture {
struct scst_device dev;
struct scst_acg acg;
struct scst_acg other_acg;
struct scst_acg_dev acg_dev;
unsigned int registrations;
int register_result;
struct completion removing;
unsigned int lun;
unsigned int lookups;
unsigned int designator_clears;
unsigned int inquiries;
int inquiry_result;
};
static unsigned int cm_lookup_stub(const struct scst_device *dev)
{
struct kunit *test = kunit_get_current_test();
struct cm_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev);
KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex));
lockdep_assert_held(&scst_mutex);
f->lookups++;
return f->lun;
}
static void cm_clear_stub(struct scst_device *dev)
{
struct kunit *test = kunit_get_current_test();
struct cm_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev);
KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex));
f->designator_clears++;
}
static int cm_inquiry_stub(struct scst_device *dev, unsigned int lun,
struct scst_cm_init_inq_priv *priv)
{
struct kunit *test = kunit_get_current_test();
struct cm_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev);
KUNIT_EXPECT_PTR_EQ(test, priv, NULL);
KUNIT_EXPECT_EQ(test, lun, f->lun);
KUNIT_EXPECT_GT(test, dev->block_count, 0);
KUNIT_EXPECT_TRUE(test, mutex_is_locked(&scst_mutex));
KUNIT_EXPECT_EQ(test, f->designator_clears, f->inquiries + 1);
f->inquiries++;
return f->inquiry_result;
}
static int cm_test_init(struct kunit *test)
{
struct cm_fixture *f;
f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL);
if (!f)
return -ENOMEM;
test->priv = f;
f->dev.virt_name = "kunit-private-cm-device";
spin_lock_init(&f->dev.dev_lock);
INIT_LIST_HEAD(&f->dev.blocked_cmd_list);
atomic_set(&f->dev.cm_update_req_cnt, 0);
init_completion(&f->removing);
f->lun = 9;
kunit_activate_static_stub(test, scst_cm_get_lun, cm_lookup_stub);
kunit_activate_static_stub(test, scst_cm_dev_free_designators, cm_clear_stub);
kunit_activate_static_stub(test, scst_cm_send_init_inquiry, cm_inquiry_stub);
return 0;
}
struct cm_update_case {
const char *name;
unsigned int pending;
int error;
bool removing;
int initial_blocks;
};
static const struct cm_update_case cm_updates[] = {
{ "single", 0 },
{ "coalesce-two", 1 },
{ "coalesce-many", 10 },
{ "inquiry-enomem", 0, -ENOMEM },
{ "inquiry-eio", 0, -EIO },
{ "preserve-existing-block", 0, -ENOMEM, false, 1 },
{ "unregistered-before-update", 0, 0, true },
{ "unregistered-before-retry", 3, 0, true },
{ "retry-enomem", 3, -ENOMEM },
{ "retry-eio", 3, -EIO },
};
static void cm_update_desc(const struct cm_update_case *p, char *desc)
{
strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(cm_update, cm_updates, cm_update_desc);
static void cm_update_test(struct kunit *test)
{
const struct cm_update_case *p = test->param_value;
struct cm_fixture *f = test->priv;
unsigned int i;
f->dev.block_count = p->initial_blocks;
if (!p->pending) {
f->inquiry_result = p->error;
if (p->removing) {
f->lun = SCST_MAX_LUN;
f->dev.remove_completion = &f->removing;
}
}
scst_cm_update_dev_start(&f->dev);
if (p->pending) {
KUNIT_ASSERT_EQ(test, f->inquiries, 1U);
for (i = 0; i < p->pending; i++)
scst_cm_update_dev_start(&f->dev);
KUNIT_EXPECT_EQ(test, f->inquiries, 1U);
KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), (int)p->pending + 1);
/* The real inquiry completion unblocks before calling update_fini. */
spin_lock_bh(&f->dev.dev_lock);
scst_unblock_dev(&f->dev);
spin_unlock_bh(&f->dev.dev_lock);
f->inquiry_result = p->error;
if (p->removing) {
f->lun = SCST_MAX_LUN;
f->dev.remove_completion = &f->removing;
}
scst_cm_update_dev_fini(&f->dev);
}
KUNIT_EXPECT_EQ(test, f->lookups, p->pending ? 2U : 1U);
KUNIT_EXPECT_EQ(test, f->inquiries,
(unsigned int)!p->removing + (unsigned int)!!p->pending);
if (p->error || p->removing) {
KUNIT_EXPECT_EQ(test, f->dev.block_count, p->initial_blocks);
KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 0);
} else {
KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 1);
spin_lock_bh(&f->dev.dev_lock);
scst_unblock_dev(&f->dev);
spin_unlock_bh(&f->dev.dev_lock);
scst_cm_update_dev_fini(&f->dev);
KUNIT_EXPECT_EQ(test, atomic_read(&f->dev.cm_update_req_cnt), 0);
KUNIT_EXPECT_EQ(test, f->dev.block_count, p->initial_blocks);
}
KUNIT_EXPECT_EQ(test, f->designator_clears, f->inquiries);
KUNIT_EXPECT_FALSE(test, completion_done(&f->removing));
}
static struct scst_acg *cm_default_group(void)
{
struct cm_fixture *f = kunit_get_current_test()->priv;
return &f->acg;
}
static int cm_register_stub(struct scst_device *dev, uint64_t lun)
{
struct kunit *test = kunit_get_current_test();
struct cm_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, dev, &f->dev);
KUNIT_EXPECT_EQ(test, lun, (u64)9);
lockdep_assert_held(&scst_mutex);
f->registrations++;
return f->register_result;
}
struct cm_lun_case {
const char *name;
bool foreign;
bool lun_readonly;
bool dev_readonly;
int register_error;
};
static const struct cm_lun_case cm_luns[] = {
{ "foreign-writable", true },
{ "foreign-readonly", true, true, true },
{ "readonly-lun", false, true },
{ "readonly-device", false, false, true },
{ "readonly-both", false, true, true },
{ "writable" },
{ "register-enomem", false, false, false, -ENOMEM },
{ "register-eio", false, false, false, -EIO },
};
static void cm_lun_desc(const struct cm_lun_case *p, char *desc)
{
strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(cm_lun, cm_luns, cm_lun_desc);
static void cm_lun_test(struct kunit *test)
{
const struct cm_lun_case *p = test->param_value;
struct cm_fixture *f = test->priv;
unsigned int flags = SCST_ADD_LUN_GEN_UA | SCST_ADD_LUN_READ_ONLY;
bool readonly = p->lun_readonly || p->dev_readonly;
bool registered = !p->foreign && !readonly;
int rc;
f->acg_dev.acg = p->foreign ? &f->other_acg : &f->acg;
f->acg_dev.dev = &f->dev;
f->acg_dev.acg_dev_rd_only = p->lun_readonly;
f->dev.dev_rd_only = p->dev_readonly;
f->register_result = p->register_error;
kunit_activate_static_stub(test, scst_cm_default_acg, cm_default_group);
kunit_activate_static_stub(test, scst_cm_dev_register, cm_register_stub);
mutex_lock(&scst_mutex);
rc = scst_cm_on_add_lun(&f->acg_dev, 9, &flags);
mutex_unlock(&scst_mutex);
KUNIT_EXPECT_EQ(test, rc, p->foreign ? 0 : (readonly ? -EINVAL : p->register_error));
KUNIT_EXPECT_EQ(test, f->registrations, (unsigned int)registered);
KUNIT_EXPECT_EQ(test, !!(flags & SCST_ADD_LUN_GEN_UA), !registered);
KUNIT_EXPECT_TRUE(test, !!(flags & SCST_ADD_LUN_READ_ONLY));
}
static struct kunit_case cm_cases[] = {
KUNIT_CASE_PARAM(cm_update_test, cm_update_gen_params),
KUNIT_CASE_PARAM(cm_lun_test, cm_lun_gen_params),
{}
};
scst_kunit_suite(cm_suite, "CM", "",
.init = cm_test_init,
.test_cases = cm_cases,
);
+1
View File
@@ -4,6 +4,7 @@
scst-kunit-scst := \
kunit/alua/suites/scst_kunit_alua.o \
kunit/cdb/suites/scst_kunit_cdb.o \
kunit/cm/suites/scst_kunit_cm.o \
kunit/event/scst_event_fixture.o \
kunit/event/suites/scst_kunit_event_import.o \
kunit/event/suites/scst_kunit_event_delivery.o \
+38 -15
View File
@@ -17,6 +17,15 @@
#endif
#include "scst_priv.h"
#include "scst_pres.h"
#include "scst_copy_mgr_test.h"
#ifndef SCST_KUNIT_ENABLED
struct scst_cm_init_inq_priv;
static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun,
struct scst_cm_init_inq_priv *priv);
static void scst_cm_update_dev_fini(struct scst_device *dev);
#endif
#undef DEFAULT_SYMBOL_NAMESPACE
#define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE
@@ -2238,9 +2247,6 @@ struct scst_cm_init_inq_priv {
struct scst_device *dev;
};
static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun,
struct scst_cm_init_inq_priv *priv);
static void scst_cm_inq_retry_fn(struct scst_cmd *cmd)
{
struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv;
@@ -2254,8 +2260,6 @@ static void scst_cm_inq_retry_fn(struct scst_cmd *cmd)
TRACE_EXIT();
}
static void scst_cm_update_dev_fini(struct scst_device *dev);
static void scst_cm_init_inq_finish(struct scst_cmd *cmd)
{
int length, page_len, off, rc;
@@ -2380,14 +2384,17 @@ out:
TRACE_EXIT();
}
static int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun,
struct scst_cm_init_inq_priv *priv)
SCST_KUNIT_VISIBLE
int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun,
struct scst_cm_init_inq_priv *priv)
{
static const uint8_t inq_cdb[6] = { INQUIRY, 1, 0x83, 0x10, 0, 0 };
__be64 lun;
struct scst_cmd *cmd;
int res = 0;
SCST_KUNIT_REDIRECT(scst_cm_send_init_inquiry, dev, unpacked_lun, priv);
TRACE_ENTRY();
if (WARN_ON_ONCE(!dev)) {
@@ -2458,11 +2465,14 @@ static bool scst_cm_is_lun_free(unsigned int lun)
}
/* scst_mutex supposed to be held */
static unsigned int scst_cm_get_lun(const struct scst_device *dev)
SCST_KUNIT_VISIBLE
unsigned int scst_cm_get_lun(const struct scst_device *dev)
{
unsigned int res = SCST_MAX_LUN;
int i;
SCST_KUNIT_REDIRECT(scst_cm_get_lun, dev);
TRACE_ENTRY();
rcu_read_lock();
@@ -2506,11 +2516,14 @@ static uint64_t scst_cm_get_free_lun(void)
return lun;
}
static int scst_cm_dev_register(struct scst_device *dev, uint64_t lun)
SCST_KUNIT_VISIBLE
int scst_cm_dev_register(struct scst_device *dev, uint64_t lun)
{
int res;
struct scst_acg_dev *acg_dev;
SCST_KUNIT_REDIRECT(scst_cm_dev_register, dev, lun);
TRACE_ENTRY();
lockdep_assert_held(&scst_mutex);
@@ -2561,10 +2574,13 @@ out_err:
goto out;
}
static void scst_cm_dev_free_designators(struct scst_device *dev)
SCST_KUNIT_VISIBLE
void scst_cm_dev_free_designators(struct scst_device *dev)
{
struct scst_cm_desig *des, *t;
SCST_KUNIT_REDIRECT(scst_cm_dev_free_designators, dev);
TRACE_ENTRY();
mutex_lock(&scst_cm_mutex);
@@ -2648,8 +2664,8 @@ out_unblock:
goto out_unlock;
}
static void
scst_cm_update_dev_start(struct scst_device *dev)
SCST_KUNIT_VISIBLE
void scst_cm_update_dev_start(struct scst_device *dev)
{
int update_req_cnt, rc;
@@ -2662,8 +2678,8 @@ scst_cm_update_dev_start(struct scst_device *dev)
atomic_set(&dev->cm_update_req_cnt, 0);
}
static void
scst_cm_update_dev_fini(struct scst_device *dev)
SCST_KUNIT_VISIBLE
void scst_cm_update_dev_fini(struct scst_device *dev)
{
int update_req_cnt, rc;
@@ -2765,6 +2781,13 @@ void scst_cm_on_del_acg(struct scst_acg *acg)
/* Nothing to do */
}
SCST_KUNIT_VISIBLE
struct scst_acg *scst_cm_default_acg(void)
{
SCST_KUNIT_REDIRECT(scst_cm_default_acg);
return scst_cm_tgt->default_acg;
}
/* scst_mutex supposed to be held */
int scst_cm_on_add_lun(struct scst_acg_dev *acg_dev, uint64_t lun, unsigned int *flags)
{
@@ -2774,7 +2797,7 @@ int scst_cm_on_add_lun(struct scst_acg_dev *acg_dev, uint64_t lun, unsigned int
lockdep_assert_held(&scst_mutex);
if (acg_dev->acg != scst_cm_tgt->default_acg)
if (acg_dev->acg != scst_cm_default_acg())
goto out;
if (acg_dev->acg_dev_rd_only || acg_dev->dev->dev_rd_only) {
+21
View File
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_COPY_MGR_TEST_H
#define SCST_COPY_MGR_TEST_H
#include "scst_test.h"
#ifdef SCST_KUNIT_ENABLED
struct scst_cm_init_inq_priv;
int scst_cm_send_init_inquiry(struct scst_device *dev, unsigned int unpacked_lun,
struct scst_cm_init_inq_priv *priv);
unsigned int scst_cm_get_lun(const struct scst_device *dev);
int scst_cm_dev_register(struct scst_device *dev, uint64_t lun);
void scst_cm_dev_free_designators(struct scst_device *dev);
void scst_cm_update_dev_start(struct scst_device *dev);
void scst_cm_update_dev_fini(struct scst_device *dev);
struct scst_acg *scst_cm_default_acg(void);
#endif /* SCST_KUNIT_ENABLED */
#endif /* SCST_COPY_MGR_TEST_H */