mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
1037 lines
24 KiB
C
1037 lines
24 KiB
C
/*
|
|
* Copyright (C) 2018 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* This file is part of LVM2.
|
|
*
|
|
* This copyrighted material is made available to anyone wishing to use,
|
|
* modify, copy, or redistribute it subject to the terms and conditions
|
|
* of the GNU General Public License v.2.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "units.h"
|
|
#include "lib/device/bcache.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#define SHOW_MOCK_CALLS 0
|
|
|
|
/*----------------------------------------------------------------
|
|
* Mock engine
|
|
*--------------------------------------------------------------*/
|
|
struct mock_engine {
|
|
struct io_engine e;
|
|
struct dm_list expected_calls;
|
|
struct dm_list issued_io;
|
|
unsigned max_io;
|
|
sector_t block_size;
|
|
};
|
|
|
|
enum method {
|
|
E_DESTROY,
|
|
E_ISSUE,
|
|
E_WAIT,
|
|
E_MAX_IO
|
|
};
|
|
|
|
struct mock_call {
|
|
struct dm_list list;
|
|
enum method m;
|
|
|
|
bool match_args;
|
|
enum dir d;
|
|
int di;
|
|
block_address b;
|
|
bool issue_r;
|
|
bool wait_r;
|
|
};
|
|
|
|
struct mock_io {
|
|
struct dm_list list;
|
|
int di;
|
|
sector_t sb;
|
|
sector_t se;
|
|
void *data;
|
|
void *context;
|
|
bool r;
|
|
};
|
|
|
|
static const char *_show_method(enum method m)
|
|
{
|
|
switch (m) {
|
|
case E_DESTROY:
|
|
return "destroy()";
|
|
case E_ISSUE:
|
|
return "issue()";
|
|
case E_WAIT:
|
|
return "wait()";
|
|
case E_MAX_IO:
|
|
return "max_io()";
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
static void _expect(struct mock_engine *e, enum method m)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = m;
|
|
mc->match_args = false;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_read(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_READ;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = true;
|
|
mc->wait_r = true;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_read_any(struct mock_engine *e)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = false;
|
|
mc->issue_r = true;
|
|
mc->wait_r = true;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_write(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_WRITE;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = true;
|
|
mc->wait_r = true;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_read_bad_issue(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_READ;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = false;
|
|
mc->wait_r = true;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_write_bad_issue(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_WRITE;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = false;
|
|
mc->wait_r = true;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_read_bad_wait(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_READ;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = true;
|
|
mc->wait_r = false;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static void _expect_write_bad_wait(struct mock_engine *e, int di, block_address b)
|
|
{
|
|
struct mock_call *mc = malloc(sizeof(*mc));
|
|
|
|
T_ASSERT(mc);
|
|
mc->m = E_ISSUE;
|
|
mc->match_args = true;
|
|
mc->d = DIR_WRITE;
|
|
mc->di = di;
|
|
mc->b = b;
|
|
mc->issue_r = true;
|
|
mc->wait_r = false;
|
|
dm_list_add(&e->expected_calls, &mc->list);
|
|
}
|
|
|
|
static struct mock_call *_match_pop(struct mock_engine *e, enum method m)
|
|
{
|
|
|
|
struct mock_call *mc;
|
|
|
|
if (dm_list_empty(&e->expected_calls))
|
|
test_fail("unexpected call to method %s\n", _show_method(m));
|
|
|
|
mc = dm_list_item(e->expected_calls.n, struct mock_call);
|
|
dm_list_del(&mc->list);
|
|
|
|
if (mc->m != m)
|
|
test_fail("expected %s, but got %s\n", _show_method(mc->m), _show_method(m));
|
|
#if SHOW_MOCK_CALLS
|
|
else
|
|
fprintf(stderr, "%s called (expected)\n", _show_method(m));
|
|
#endif
|
|
|
|
return mc;
|
|
}
|
|
|
|
static void _match(struct mock_engine *e, enum method m)
|
|
{
|
|
free(_match_pop(e, m));
|
|
}
|
|
|
|
static void _no_outstanding_expectations(struct mock_engine *e)
|
|
{
|
|
struct mock_call *mc;
|
|
|
|
if (!dm_list_empty(&e->expected_calls)) {
|
|
fprintf(stderr, "unsatisfied expectations:\n");
|
|
dm_list_iterate_items (mc, &e->expected_calls)
|
|
fprintf(stderr, " %s\n", _show_method(mc->m));
|
|
}
|
|
T_ASSERT(dm_list_empty(&e->expected_calls));
|
|
}
|
|
|
|
static struct mock_engine *_to_mock(struct io_engine *e)
|
|
{
|
|
return container_of(e, struct mock_engine, e);
|
|
}
|
|
|
|
static void _mock_destroy(struct io_engine *e)
|
|
{
|
|
struct mock_engine *me = _to_mock(e);
|
|
|
|
_match(me, E_DESTROY);
|
|
T_ASSERT(dm_list_empty(&me->issued_io));
|
|
T_ASSERT(dm_list_empty(&me->expected_calls));
|
|
free(_to_mock(e));
|
|
}
|
|
|
|
static bool _mock_issue(struct io_engine *e, enum dir d, int di,
|
|
sector_t sb, sector_t se, void *data, void *context)
|
|
{
|
|
bool r, wait_r;
|
|
struct mock_io *io;
|
|
struct mock_call *mc;
|
|
struct mock_engine *me = _to_mock(e);
|
|
|
|
mc = _match_pop(me, E_ISSUE);
|
|
if (mc->match_args) {
|
|
T_ASSERT(d == mc->d);
|
|
T_ASSERT(di == mc->di);
|
|
T_ASSERT(sb == mc->b * me->block_size);
|
|
T_ASSERT(se == (mc->b + 1) * me->block_size);
|
|
}
|
|
r = mc->issue_r;
|
|
wait_r = mc->wait_r;
|
|
free(mc);
|
|
|
|
if (r) {
|
|
io = malloc(sizeof(*io));
|
|
if (!io)
|
|
abort();
|
|
|
|
io->di = di;
|
|
io->sb = sb;
|
|
io->se = se;
|
|
io->data = data;
|
|
io->context = context;
|
|
io->r = wait_r;
|
|
|
|
dm_list_add(&me->issued_io, &io->list);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static bool _mock_wait(struct io_engine *e, io_complete_fn fn)
|
|
{
|
|
struct mock_io *io;
|
|
struct mock_engine *me = _to_mock(e);
|
|
_match(me, E_WAIT);
|
|
|
|
// FIXME: provide a way to control how many are completed and whether
|
|
// they error.
|
|
T_ASSERT(!dm_list_empty(&me->issued_io));
|
|
io = dm_list_item(me->issued_io.n, struct mock_io);
|
|
dm_list_del(&io->list);
|
|
fn(io->context, io->r ? 0 : -EIO);
|
|
free(io);
|
|
|
|
return true;
|
|
}
|
|
|
|
static unsigned _mock_max_io(struct io_engine *e)
|
|
{
|
|
struct mock_engine *me = _to_mock(e);
|
|
_match(me, E_MAX_IO);
|
|
return me->max_io;
|
|
}
|
|
|
|
static struct mock_engine *_mock_create(unsigned max_io, sector_t block_size)
|
|
{
|
|
struct mock_engine *m = malloc(sizeof(*m));
|
|
|
|
T_ASSERT(m);
|
|
|
|
m->e.destroy = _mock_destroy;
|
|
m->e.issue = _mock_issue;
|
|
m->e.wait = _mock_wait;
|
|
m->e.max_io = _mock_max_io;
|
|
|
|
m->max_io = max_io;
|
|
m->block_size = block_size;
|
|
dm_list_init(&m->expected_calls);
|
|
dm_list_init(&m->issued_io);
|
|
|
|
return m;
|
|
}
|
|
|
|
/*----------------------------------------------------------------
|
|
* Fixtures
|
|
*--------------------------------------------------------------*/
|
|
struct fixture {
|
|
struct mock_engine *me;
|
|
struct bcache *cache;
|
|
};
|
|
|
|
static struct fixture *_fixture_init(sector_t block_size, unsigned nr_cache_blocks)
|
|
{
|
|
struct fixture *f = malloc(sizeof(*f));
|
|
|
|
T_ASSERT(f);
|
|
|
|
f->me = _mock_create(16, block_size);
|
|
T_ASSERT(f->me);
|
|
|
|
_expect(f->me, E_MAX_IO);
|
|
f->cache = bcache_create(block_size, nr_cache_blocks, &f->me->e);
|
|
T_ASSERT(f->cache);
|
|
|
|
return f;
|
|
}
|
|
|
|
static void _fixture_exit(struct fixture *f)
|
|
{
|
|
if (f) {
|
|
_expect(f->me, E_DESTROY);
|
|
bcache_destroy(f->cache);
|
|
|
|
free(f);
|
|
}
|
|
}
|
|
|
|
static void *_small_fixture_init(void)
|
|
{
|
|
return _fixture_init(128, 16);
|
|
}
|
|
|
|
static void _small_fixture_exit(void *context)
|
|
{
|
|
_fixture_exit(context);
|
|
}
|
|
|
|
static void *_large_fixture_init(void)
|
|
{
|
|
return _fixture_init(128, 1024);
|
|
}
|
|
|
|
static void _large_fixture_exit(void *context)
|
|
{
|
|
_fixture_exit(context);
|
|
}
|
|
|
|
/*----------------------------------------------------------------
|
|
* Tests
|
|
*--------------------------------------------------------------*/
|
|
#define MEG 2048
|
|
#define SECTOR_SHIFT 9
|
|
#define PAGE_SIZE_SECTORS ((PAGE_SIZE) >> SECTOR_SHIFT)
|
|
|
|
static void good_create(sector_t block_size, unsigned nr_cache_blocks)
|
|
{
|
|
struct bcache *cache;
|
|
struct mock_engine *me = _mock_create(16, 128);
|
|
|
|
_expect(me, E_MAX_IO);
|
|
cache = bcache_create(block_size, nr_cache_blocks, &me->e);
|
|
T_ASSERT(cache);
|
|
|
|
_expect(me, E_DESTROY);
|
|
bcache_destroy(cache);
|
|
}
|
|
|
|
static void bad_create(sector_t block_size, unsigned nr_cache_blocks)
|
|
{
|
|
struct bcache *cache;
|
|
struct mock_engine *me = _mock_create(16, 128);
|
|
|
|
_expect(me, E_MAX_IO);
|
|
cache = bcache_create(block_size, nr_cache_blocks, &me->e);
|
|
T_ASSERT(!cache);
|
|
|
|
_expect(me, E_DESTROY);
|
|
me->e.destroy(&me->e);
|
|
}
|
|
|
|
static void test_create(void *fixture)
|
|
{
|
|
good_create(PAGE_SIZE_SECTORS, 16);
|
|
}
|
|
|
|
static void test_nr_cache_blocks_must_be_positive(void *fixture)
|
|
{
|
|
bad_create(PAGE_SIZE_SECTORS, 0);
|
|
}
|
|
|
|
static void test_block_size_must_be_positive(void *fixture)
|
|
{
|
|
bad_create(0, 16);
|
|
}
|
|
|
|
static void test_block_size_must_be_multiple_of_page_size(void *fixture)
|
|
{
|
|
static const unsigned _bad_examples[] = {3, 9, 13, 1025};
|
|
|
|
unsigned i;
|
|
|
|
for (i = 0; i < DM_ARRAY_SIZE(_bad_examples); i++)
|
|
bad_create(_bad_examples[i], 16);
|
|
|
|
for (i = 1; i < 100; i++)
|
|
good_create(i * PAGE_SIZE_SECTORS, 16);
|
|
}
|
|
|
|
static void test_get_triggers_read(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
|
|
int di = 17; // arbitrary key
|
|
struct block *b;
|
|
|
|
_expect_read(f->me, di, 0);
|
|
_expect(f->me, E_WAIT);
|
|
T_ASSERT(bcache_get(f->cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
|
|
_expect_read(f->me, di, 1);
|
|
_expect(f->me, E_WAIT);
|
|
T_ASSERT(bcache_get(f->cache, di, 1, GF_DIRTY, &b));
|
|
_expect_write(f->me, di, 1);
|
|
_expect(f->me, E_WAIT);
|
|
bcache_put(b);
|
|
}
|
|
|
|
static void test_repeated_reads_are_cached(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
|
|
int di = 17; // arbitrary key
|
|
unsigned i;
|
|
struct block *b;
|
|
|
|
_expect_read(f->me, di, 0);
|
|
_expect(f->me, E_WAIT);
|
|
for (i = 0; i < 100; i++) {
|
|
T_ASSERT(bcache_get(f->cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
}
|
|
|
|
static void test_block_gets_evicted_with_many_reads(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
const unsigned nr_cache_blocks = 16;
|
|
|
|
int di = 17; // arbitrary key
|
|
unsigned i;
|
|
struct block *b;
|
|
|
|
for (i = 0; i < nr_cache_blocks; i++) {
|
|
_expect_read(me, di, i);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, i, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
// Not enough cache blocks to hold this one
|
|
_expect_read(me, di, nr_cache_blocks);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, nr_cache_blocks, 0, &b));
|
|
bcache_put(b);
|
|
|
|
// Now if we run through we should find one block has been
|
|
// evicted. We go backwards because the oldest is normally
|
|
// evicted first.
|
|
_expect_read_any(me);
|
|
_expect(me, E_WAIT);
|
|
for (i = nr_cache_blocks; i; i--) {
|
|
T_ASSERT(bcache_get(cache, di, i - 1, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
}
|
|
|
|
static void test_prefetch_issues_a_read(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
const unsigned nr_cache_blocks = 16;
|
|
|
|
int di = 17; // arbitrary key
|
|
unsigned i;
|
|
struct block *b;
|
|
|
|
for (i = 0; i < nr_cache_blocks; i++) {
|
|
// prefetch should not wait
|
|
_expect_read(me, di, i);
|
|
bcache_prefetch(cache, di, i);
|
|
}
|
|
_no_outstanding_expectations(me);
|
|
|
|
for (i = 0; i < nr_cache_blocks; i++) {
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, i, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
}
|
|
|
|
static void test_too_many_prefetches_does_not_trigger_a_wait(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
|
|
const unsigned nr_cache_blocks = 16;
|
|
int di = 17; // arbitrary key
|
|
unsigned i;
|
|
|
|
for (i = 0; i < 10 * nr_cache_blocks; i++) {
|
|
// prefetch should not wait
|
|
if (i < nr_cache_blocks)
|
|
_expect_read(me, di, i);
|
|
bcache_prefetch(cache, di, i);
|
|
}
|
|
|
|
// Destroy will wait for any in flight IO triggered by prefetches.
|
|
for (i = 0; i < nr_cache_blocks; i++)
|
|
_expect(me, E_WAIT);
|
|
}
|
|
|
|
static void test_dirty_data_gets_written_back(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
|
|
int di = 17; // arbitrary key
|
|
struct block *b;
|
|
|
|
// Expect the read
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_DIRTY, &b));
|
|
bcache_put(b);
|
|
|
|
// Expect the write
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
}
|
|
|
|
static void test_zeroed_data_counts_as_dirty(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
|
|
int di = 17; // arbitrary key
|
|
struct block *b;
|
|
|
|
// No read
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
// Expect the write
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
}
|
|
|
|
static void test_flush_waits_for_all_dirty(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
|
|
const unsigned count = 16;
|
|
int di = 17; // arbitrary key
|
|
unsigned i;
|
|
struct block *b;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (i % 2) {
|
|
T_ASSERT(bcache_get(cache, di, i, GF_ZERO, &b));
|
|
} else {
|
|
_expect_read(me, di, i);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, i, 0, &b));
|
|
}
|
|
bcache_put(b);
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (i % 2)
|
|
_expect_write(me, di, i);
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (i % 2)
|
|
_expect(me, E_WAIT);
|
|
}
|
|
|
|
T_ASSERT(bcache_flush(cache));
|
|
_no_outstanding_expectations(me);
|
|
}
|
|
|
|
static void test_multiple_files(void *context)
|
|
{
|
|
static int _dis[] = {1, 128, 345, 678, 890};
|
|
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < DM_ARRAY_SIZE(_dis); i++) {
|
|
_expect_read(me, _dis[i], 0);
|
|
_expect(me, E_WAIT);
|
|
|
|
T_ASSERT(bcache_get(cache, _dis[i], 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
}
|
|
|
|
static void test_read_bad_issue(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
|
|
_expect_read_bad_issue(me, 17, 0);
|
|
T_ASSERT(!bcache_get(cache, 17, 0, 0, &b));
|
|
}
|
|
|
|
static void test_read_bad_issue_intermittent(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read_bad_issue(me, di, 0);
|
|
T_ASSERT(!bcache_get(cache, di, 0, 0, &b));
|
|
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
static void test_read_bad_wait(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read_bad_wait(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(!bcache_get(cache, di, 0, 0, &b));
|
|
}
|
|
|
|
static void test_read_bad_wait_intermittent(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read_bad_wait(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(!bcache_get(cache, di, 0, 0, &b));
|
|
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
static void test_write_bad_issue_stops_flush(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
_expect_write_bad_issue(me, di, 0);
|
|
bcache_put(b);
|
|
T_ASSERT(!bcache_flush(cache));
|
|
|
|
// we'll let it succeed the second time
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_flush(cache));
|
|
}
|
|
|
|
static void test_write_bad_io_stops_flush(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
_expect_write_bad_wait(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
bcache_put(b);
|
|
T_ASSERT(!bcache_flush(cache));
|
|
|
|
// we'll let it succeed the second time
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_flush(cache));
|
|
}
|
|
|
|
static void test_invalidate_not_present(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct bcache *cache = f->cache;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_invalidate(cache, di, 0));
|
|
}
|
|
|
|
static void test_invalidate_present(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
|
|
T_ASSERT(bcache_invalidate(cache, di, 0));
|
|
}
|
|
|
|
static void test_invalidate_after_read_error(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read_bad_issue(me, di, 0);
|
|
T_ASSERT(!bcache_get(cache, di, 0, 0, &b));
|
|
T_ASSERT(bcache_invalidate(cache, di, 0));
|
|
}
|
|
|
|
static void test_invalidate_after_write_error(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
// invalidate should fail if the write fails
|
|
_expect_write_bad_wait(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(!bcache_invalidate(cache, di, 0));
|
|
|
|
// and should succeed if the write does
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_invalidate(cache, di, 0));
|
|
|
|
// a read is not required to get the block
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
static void test_invalidate_held_block(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
|
|
T_ASSERT(!bcache_invalidate(cache, di, 0));
|
|
|
|
_expect_write(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
bcache_put(b);
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// abort tests
|
|
|
|
static void test_abort_no_blocks(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct bcache *cache = f->cache;
|
|
int di = 17;
|
|
|
|
// We have no expectations
|
|
bcache_abort_di(cache, di);
|
|
}
|
|
|
|
static void test_abort_single_block(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
bcache_abort_di(cache, di);
|
|
|
|
// no write should be issued
|
|
T_ASSERT(bcache_flush(cache));
|
|
}
|
|
|
|
static void test_abort_forces_reread(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di = 17;
|
|
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, GF_DIRTY, &b));
|
|
bcache_put(b);
|
|
|
|
bcache_abort_di(cache, di);
|
|
T_ASSERT(bcache_flush(cache));
|
|
|
|
// Check the block is re-read
|
|
_expect_read(me, di, 0);
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, di, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
static void test_abort_only_specific_di(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
struct block *b;
|
|
int di1 = 17, di2 = 18;
|
|
|
|
T_ASSERT(bcache_get(cache, di1, 0, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
T_ASSERT(bcache_get(cache, di1, 1, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
T_ASSERT(bcache_get(cache, di2, 0, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
T_ASSERT(bcache_get(cache, di2, 1, GF_ZERO, &b));
|
|
bcache_put(b);
|
|
|
|
bcache_abort_di(cache, di2);
|
|
|
|
// writes for di1 should still be issued
|
|
_expect_write(me, di1, 0);
|
|
_expect_write(me, di1, 1);
|
|
|
|
_expect(me, E_WAIT);
|
|
_expect(me, E_WAIT);
|
|
|
|
T_ASSERT(bcache_flush(cache));
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Chasing a bug reported by dct
|
|
|
|
static void _cycle(struct fixture *f, unsigned nr_cache_blocks)
|
|
{
|
|
struct mock_engine *me = f->me;
|
|
struct bcache *cache = f->cache;
|
|
|
|
unsigned i;
|
|
struct block *b;
|
|
|
|
for (i = 0; i < nr_cache_blocks; i++) {
|
|
// prefetch should not wait
|
|
_expect_read(me, i, 0);
|
|
bcache_prefetch(cache, i, 0);
|
|
}
|
|
|
|
// This double checks the reads occur in response to the prefetch
|
|
_no_outstanding_expectations(me);
|
|
|
|
for (i = 0; i < nr_cache_blocks; i++) {
|
|
_expect(me, E_WAIT);
|
|
T_ASSERT(bcache_get(cache, i, 0, 0, &b));
|
|
bcache_put(b);
|
|
}
|
|
|
|
_no_outstanding_expectations(me);
|
|
}
|
|
|
|
static void test_concurrent_reads_after_invalidate(void *context)
|
|
{
|
|
struct fixture *f = context;
|
|
unsigned i, nr_cache_blocks = 16;
|
|
|
|
_cycle(f, nr_cache_blocks);
|
|
for (i = 0; i < nr_cache_blocks; i++)
|
|
bcache_invalidate_di(f->cache, i);
|
|
_cycle(f, nr_cache_blocks);
|
|
}
|
|
|
|
/*----------------------------------------------------------------
|
|
* Top level
|
|
*--------------------------------------------------------------*/
|
|
#define T(path, desc, fn) register_test(ts, "/base/device/bcache/" path, desc, fn)
|
|
|
|
static struct test_suite *_tiny_tests(void)
|
|
{
|
|
struct test_suite *ts = test_suite_create(NULL, NULL);
|
|
if (!ts) {
|
|
fprintf(stderr, "out of memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
T("create-destroy", "simple create/destroy", test_create);
|
|
T("cache-blocks-positive", "nr cache blocks must be positive", test_nr_cache_blocks_must_be_positive);
|
|
T("block-size-positive", "block size must be positive", test_block_size_must_be_positive);
|
|
T("block-size-multiple-page", "block size must be a multiple of page size", test_block_size_must_be_multiple_of_page_size);
|
|
|
|
return ts;
|
|
}
|
|
|
|
static struct test_suite *_small_tests(void)
|
|
{
|
|
struct test_suite *ts = test_suite_create(_small_fixture_init, _small_fixture_exit);
|
|
if (!ts) {
|
|
fprintf(stderr, "out of memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
T("get-reads", "bcache_get() triggers read", test_get_triggers_read);
|
|
T("reads-cached", "repeated reads are cached", test_repeated_reads_are_cached);
|
|
T("blocks-get-evicted", "block get evicted with many reads", test_block_gets_evicted_with_many_reads);
|
|
T("prefetch-reads", "prefetch issues a read", test_prefetch_issues_a_read);
|
|
T("prefetch-never-waits", "too many prefetches does not trigger a wait", test_too_many_prefetches_does_not_trigger_a_wait);
|
|
T("writeback-occurs", "dirty data gets written back", test_dirty_data_gets_written_back);
|
|
T("zero-flag-dirties", "zeroed data counts as dirty", test_zeroed_data_counts_as_dirty);
|
|
T("read-multiple-files", "read from multiple files", test_multiple_files);
|
|
T("read-bad-issue", "read fails if io engine unable to issue", test_read_bad_issue);
|
|
T("read-bad-issue-intermittent", "failed issue, followed by succes", test_read_bad_issue_intermittent);
|
|
T("read-bad-io", "read issued ok, but io fails", test_read_bad_wait);
|
|
T("read-bad-io-intermittent", "failed io, followed by success", test_read_bad_wait_intermittent);
|
|
T("write-bad-issue-stops-flush", "flush fails temporarily if any block fails to write", test_write_bad_issue_stops_flush);
|
|
T("write-bad-io-stops-flush", "flush fails temporarily if any block fails to write", test_write_bad_io_stops_flush);
|
|
T("invalidate-not-present", "invalidate a block that isn't in the cache", test_invalidate_not_present);
|
|
T("invalidate-present", "invalidate a block that is in the cache", test_invalidate_present);
|
|
T("invalidate-read-error", "invalidate a block that errored", test_invalidate_after_read_error);
|
|
T("invalidate-write-error", "invalidate a block that errored", test_invalidate_after_write_error);
|
|
T("invalidate-fails-in-held", "invalidating a held block fails", test_invalidate_held_block);
|
|
|
|
T("abort-with-no-blocks", "you can call abort, even if there are no blocks in the cache", test_abort_no_blocks);
|
|
T("abort-single-block", "single block get silently discarded", test_abort_single_block);
|
|
T("abort-forces-read", "if a block has been discarded then another read is necc.", test_abort_forces_reread);
|
|
T("abort-specific-di", "abort doesn't effect other dis", test_abort_only_specific_di);
|
|
|
|
T("concurrent-reads-after-invalidate", "prefetch should still issue concurrent reads after invalidate",
|
|
test_concurrent_reads_after_invalidate);
|
|
|
|
return ts;
|
|
}
|
|
|
|
static struct test_suite *_large_tests(void)
|
|
{
|
|
struct test_suite *ts = test_suite_create(_large_fixture_init, _large_fixture_exit);
|
|
if (!ts) {
|
|
fprintf(stderr, "out of memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
T("flush-waits", "flush waits for all dirty", test_flush_waits_for_all_dirty);
|
|
|
|
return ts;
|
|
}
|
|
|
|
void bcache_tests(struct dm_list *all_tests)
|
|
{
|
|
dm_list_add(all_tests, &_tiny_tests()->list);
|
|
dm_list_add(all_tests, &_small_tests()->list);
|
|
dm_list_add(all_tests, &_large_tests()->list);
|
|
}
|