blob: 5ebc411ea749a0b00297c9e2d69f8a1cfb3d6a77 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for the errseq_t error-tracking infrastructure.
*
* Most cases exercise the documented single-threaded semantics of the
* errseq API (see Documentation/core-api/errseq.rst and lib/errseq.c): error
* recording and overwriting, the "seen" handoff between errseq_sample()
* and errseq_check_and_advance(), and the re-reporting of an error that
* is recorded again after it has been seen.
*
* The WARN path for invalid error values is not tested. The last case
* races errseq_set() against errseq_check_and_advance() on another CPU.
*/
#include <kunit/test.h>
#include <linux/atomic.h>
#include <linux/cpumask.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/errseq.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
#include <linux/random.h>
#include <linux/sched.h>
/*
* A zeroed errseq_t is the "no error has ever occurred" epoch: it
* samples as zero and no check against it reports anything.
*/
static void errseq_test_zero_epoch_reports_no_error(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = 0;
KUNIT_EXPECT_EQ(test, errseq_sample(&eseq), 0);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), 0);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0);
KUNIT_EXPECT_EQ(test, since, 0);
}
static void errseq_test_set_records_error(struct kunit *test)
{
errseq_t eseq = 0;
/* errseq_set() returns the previous value; the epoch is zero. */
KUNIT_EXPECT_EQ(test, errseq_set(&eseq, -EIO), 0);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), -EIO);
}
/* Any error set always overwrites an existing error. */
static void errseq_test_set_overwrites_error(struct kunit *test)
{
errseq_t eseq = 0;
errseq_set(&eseq, -EIO);
errseq_set(&eseq, -ENOSPC);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), -ENOSPC);
}
/* Both ends of the valid error range are recorded exactly. */
static void errseq_test_errno_range_extremes(struct kunit *test)
{
errseq_t lo = 0;
errseq_t hi = 0;
errseq_set(&lo, -1);
KUNIT_EXPECT_EQ(test, errseq_check(&lo, 0), -1);
errseq_set(&hi, -MAX_ERRNO);
KUNIT_EXPECT_EQ(test, errseq_check(&hi, 0), -MAX_ERRNO);
}
/*
* An error nobody has seen yet samples as zero, so that a check against
* the sample still reports it (see commit b4678df184b3 ("errseq: Always
* report a writeback error once")).
*/
static void errseq_test_sample_of_unseen_error_is_zero(struct kunit *test)
{
errseq_t eseq = 0;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_sample(&eseq), 0);
}
static void errseq_test_new_sampler_sees_unseen_error(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since;
errseq_set(&eseq, -EIO);
since = errseq_sample(&eseq);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, since), -EIO);
}
/* A given error is reported exactly once per advancing cursor. */
static void errseq_test_check_and_advance_reports_once(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = errseq_sample(&eseq);
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0);
}
/*
* Once an error has been seen, a fresh sample is non-zero and checking
* against it reports nothing: handled errors do not reach new samplers.
*/
static void errseq_test_sample_after_seen_is_current(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = 0;
errseq_t sample;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
sample = errseq_sample(&eseq);
KUNIT_EXPECT_NE(test, sample, 0);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, sample), 0);
}
static void errseq_test_new_error_after_advance(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = 0;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
errseq_set(&eseq, -ENOSPC);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -ENOSPC);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0);
}
/*
* Recording the same error again after it has been seen must bump the
* sequence, so cursors that consumed the first occurrence see the
* second one too.
*/
static void errseq_test_same_error_reported_again_after_seen(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = 0;
errseq_t seen_cursor;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
seen_cursor = since;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, since), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
/* The repeat must advance the sequence, not just re-toggle "seen". */
KUNIT_EXPECT_NE(test, since, seen_cursor);
}
/*
* A cursor that consumed an error must still observe a repeat of that
* error even when another cursor has already marked the repeat seen:
* recording over a seen value must advance the sequence.
*/
static void errseq_test_repeat_error_visible_to_all_cursors(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t cursor_a = 0;
errseq_t cursor_b = 0;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO);
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, cursor_a), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), 0);
}
/* An advance with no new error reports nothing and leaves the cursor put. */
static void errseq_test_advance_stable_when_unchanged(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t since = 0;
errseq_t cursor;
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO);
cursor = since;
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0);
KUNIT_EXPECT_EQ(test, since, cursor);
}
/*
* Cursors are independent: one subscriber consuming an error does not
* consume it for another, and each subscriber sees each error once.
*/
static void errseq_test_two_subscribers_independent(struct kunit *test)
{
errseq_t eseq = 0;
errseq_t cursor_a = errseq_sample(&eseq);
errseq_t cursor_b = errseq_sample(&eseq);
errseq_set(&eseq, -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check(&eseq, cursor_b), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), -EIO);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), 0);
KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), 0);
}
/*
* errseq_check_and_advance() racing with errseq_set() on another CPU.
*
* Each round, cursor @f checks an unseen -EIO while another CPU records
* -ENOSPC. Afterwards -EIO is recorded again and cursor @g checks. The
* second -EIO is recorded after @f's check returned, so @f's next check
* must report an error, whichever way the race with -ENOSPC went.
*/
struct errseq_race {
errseq_t eseq;
atomic_t go; /* round the writer should run */
atomic_t done; /* last round the writer completed */
unsigned int wdelay;
};
static void errseq_race_spin(unsigned int n)
{
while (n--)
cpu_relax();
}
static int errseq_race_writer(void *data)
{
struct errseq_race *r = data;
int seen = 0;
while (!kthread_should_stop()) {
int round = atomic_read_acquire(&r->go);
if (round == seen) {
cond_resched();
continue;
}
seen = round;
errseq_race_spin(READ_ONCE(r->wdelay));
errseq_set(&r->eseq, -ENOSPC);
atomic_set_release(&r->done, round);
}
return 0;
}
#define ERRSEQ_RACE_ROUNDS 2000000
#define ERRSEQ_RACE_SECONDS 5
static void errseq_test_check_and_advance_race(struct kunit *test)
{
struct errseq_race *r;
struct task_struct *writer;
unsigned long deadline;
int round, unreported = 0, missed = 0;
if (num_online_cpus() < 2)
kunit_skip(test, "needs at least two CPUs");
r = kunit_kzalloc(test, sizeof(*r), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, r);
writer = kthread_run(errseq_race_writer, r, "errseq_race");
KUNIT_ASSERT_FALSE(test, IS_ERR(writer));
deadline = jiffies + ERRSEQ_RACE_SECONDS * HZ;
for (round = 1; round <= ERRSEQ_RACE_ROUNDS; round++) {
errseq_t f = 0, g = 0;
WRITE_ONCE(r->eseq, 0);
errseq_set(&r->eseq, -EIO);
WRITE_ONCE(r->wdelay, get_random_u32_below(64));
atomic_set_release(&r->go, round);
errseq_race_spin(get_random_u32_below(64));
if (!errseq_check_and_advance(&r->eseq, &f))
unreported++;
while (atomic_read_acquire(&r->done) != round)
cond_resched();
/* Recorded after f's check returned: f must hear about it. */
errseq_set(&r->eseq, -EIO);
errseq_check_and_advance(&r->eseq, &g);
if (!errseq_check_and_advance(&r->eseq, &f))
missed++;
if (time_after(jiffies, deadline))
break;
if (!(round & 1023))
cond_resched();
}
kthread_stop(writer);
kunit_info(test, "%d rounds, %d missed errors\n", round - 1, missed);
KUNIT_EXPECT_EQ(test, unreported, 0);
KUNIT_EXPECT_EQ(test, missed, 0);
}
static struct kunit_case errseq_test_cases[] = {
KUNIT_CASE(errseq_test_zero_epoch_reports_no_error),
KUNIT_CASE(errseq_test_set_records_error),
KUNIT_CASE(errseq_test_set_overwrites_error),
KUNIT_CASE(errseq_test_errno_range_extremes),
KUNIT_CASE(errseq_test_sample_of_unseen_error_is_zero),
KUNIT_CASE(errseq_test_new_sampler_sees_unseen_error),
KUNIT_CASE(errseq_test_check_and_advance_reports_once),
KUNIT_CASE(errseq_test_sample_after_seen_is_current),
KUNIT_CASE(errseq_test_new_error_after_advance),
KUNIT_CASE(errseq_test_same_error_reported_again_after_seen),
KUNIT_CASE(errseq_test_repeat_error_visible_to_all_cursors),
KUNIT_CASE(errseq_test_advance_stable_when_unchanged),
KUNIT_CASE(errseq_test_two_subscribers_independent),
KUNIT_CASE_SLOW(errseq_test_check_and_advance_race),
{}
};
static struct kunit_suite errseq_test_suite = {
.name = "errseq",
.test_cases = errseq_test_cases,
};
kunit_test_suite(errseq_test_suite);
MODULE_DESCRIPTION("KUnit tests for the errseq infrastructure");
MODULE_LICENSE("GPL");