mirror of
git://sourceware.org/git/lvm2.git
synced 2025-03-30 10:50:34 +03:00
[device/bcache] More tests and some bug fixes
This commit is contained in:
parent
8ae3b244fc
commit
467adfa082
@ -130,44 +130,21 @@ static struct control_block *_iocb_to_cb(struct iocb *icb)
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// FIXME: write a sync engine too
|
||||
enum dir {
|
||||
DIR_READ,
|
||||
DIR_WRITE
|
||||
};
|
||||
|
||||
struct io_engine {
|
||||
struct async_engine {
|
||||
struct io_engine e;
|
||||
io_context_t aio_context;
|
||||
struct cb_set *cbs;
|
||||
};
|
||||
|
||||
static struct io_engine *_engine_create(unsigned max_io)
|
||||
static struct async_engine *_to_async(struct io_engine *e)
|
||||
{
|
||||
int r;
|
||||
struct io_engine *e = dm_malloc(sizeof(*e));
|
||||
|
||||
if (!e)
|
||||
return NULL;
|
||||
|
||||
e->aio_context = 0;
|
||||
r = io_setup(max_io, &e->aio_context);
|
||||
if (r < 0) {
|
||||
log_warn("io_setup failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
e->cbs = _cb_set_create(max_io);
|
||||
if (!e->cbs) {
|
||||
log_warn("couldn't create control block set");
|
||||
dm_free(e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return e;
|
||||
return container_of(e, struct async_engine, e);
|
||||
}
|
||||
|
||||
static void _engine_destroy(struct io_engine *e)
|
||||
static void _async_destroy(struct io_engine *ioe)
|
||||
{
|
||||
int r;
|
||||
struct async_engine *e = _to_async(ioe);
|
||||
|
||||
_cb_set_destroy(e->cbs);
|
||||
|
||||
@ -179,12 +156,13 @@ static void _engine_destroy(struct io_engine *e)
|
||||
dm_free(e);
|
||||
}
|
||||
|
||||
static bool _engine_issue(struct io_engine *e, enum dir d, int fd,
|
||||
sector_t sb, sector_t se, void *data, void *context)
|
||||
static bool _async_issue(struct io_engine *ioe, enum dir d, int fd,
|
||||
sector_t sb, sector_t se, void *data, void *context)
|
||||
{
|
||||
int r;
|
||||
struct iocb *cb_array[1];
|
||||
struct control_block *cb;
|
||||
struct async_engine *e = _to_async(ioe);
|
||||
|
||||
if (((uint64_t) data) & (PAGE_SIZE - 1)) {
|
||||
log_warn("misaligned data buffer");
|
||||
@ -218,13 +196,13 @@ static bool _engine_issue(struct io_engine *e, enum dir d, int fd,
|
||||
|
||||
#define MAX_IO 1024
|
||||
#define MAX_EVENT 64
|
||||
typedef void complete_fn(void *context, int io_error);
|
||||
|
||||
static bool _engine_wait(struct io_engine *e, complete_fn fn)
|
||||
static bool _async_wait(struct io_engine *ioe, io_complete_fn fn)
|
||||
{
|
||||
int i, r;
|
||||
struct io_event event[MAX_EVENT];
|
||||
struct control_block *cb;
|
||||
struct async_engine *e = _to_async(ioe);
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
r = io_getevents(e->aio_context, 1, MAX_EVENT, event, NULL);
|
||||
@ -255,6 +233,36 @@ static bool _engine_wait(struct io_engine *e, complete_fn fn)
|
||||
return true;
|
||||
}
|
||||
|
||||
struct io_engine *create_async_io_engine(unsigned max_io)
|
||||
{
|
||||
int r;
|
||||
struct async_engine *e = dm_malloc(sizeof(*e));
|
||||
|
||||
if (!e)
|
||||
return NULL;
|
||||
|
||||
e->e.destroy = _async_destroy;
|
||||
e->e.issue = _async_issue;
|
||||
e->e.wait = _async_wait;
|
||||
|
||||
e->aio_context = 0;
|
||||
r = io_setup(max_io, &e->aio_context);
|
||||
if (r < 0) {
|
||||
log_warn("io_setup failed");
|
||||
dm_free(e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
e->cbs = _cb_set_create(max_io);
|
||||
if (!e->cbs) {
|
||||
log_warn("couldn't create control block set");
|
||||
dm_free(e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &e->e;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#define MIN_BLOCKS 16
|
||||
@ -536,7 +544,9 @@ static bool _issue_low_level(struct block *b, enum dir d)
|
||||
return false;
|
||||
|
||||
_set_flags(b, BF_IO_PENDING);
|
||||
if (!_engine_issue(cache->engine, d, b->fd, sb, se, b->data, b)) {
|
||||
dm_list_add(&cache->io_pending, &b->list);
|
||||
|
||||
if (!cache->engine->issue(cache->engine, d, b->fd, sb, se, b->data, b)) {
|
||||
_complete_io(b, -EIO);
|
||||
return false;
|
||||
}
|
||||
@ -557,7 +567,7 @@ static inline bool _issue_write(struct block *b)
|
||||
|
||||
static bool _wait_io(struct bcache *cache)
|
||||
{
|
||||
return _engine_wait(cache->engine, _complete_io);
|
||||
return cache->engine->wait(cache->engine, _complete_io);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
@ -614,17 +624,20 @@ static struct block *_find_unused_clean_block(struct bcache *cache)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct block *_new_block(struct bcache *cache, int fd, block_address index)
|
||||
static struct block *_new_block(struct bcache *cache, int fd, block_address index, bool can_wait)
|
||||
{
|
||||
struct block *b;
|
||||
|
||||
b = _alloc_block(cache);
|
||||
while (!b && cache->nr_locked < cache->nr_cache_blocks) {
|
||||
while (!b && !dm_list_empty(&cache->clean)) {
|
||||
b = _find_unused_clean_block(cache);
|
||||
if (!b) {
|
||||
if (dm_list_empty(&cache->io_pending))
|
||||
_writeback(cache, 16);
|
||||
_wait_io(cache);
|
||||
if (can_wait) {
|
||||
if (dm_list_empty(&cache->io_pending))
|
||||
_writeback(cache, 16); // FIXME: magic number
|
||||
_wait_io(cache);
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -702,7 +715,7 @@ static struct block *_lookup_or_read_block(struct bcache *cache,
|
||||
} else {
|
||||
_miss(cache, flags);
|
||||
|
||||
b = _new_block(cache, fd, index);
|
||||
b = _new_block(cache, fd, index, true);
|
||||
if (b) {
|
||||
if (flags & GF_ZERO)
|
||||
_zero_block(b);
|
||||
@ -741,9 +754,11 @@ static void _preemptive_writeback(struct bcache *cache)
|
||||
/*----------------------------------------------------------------
|
||||
* Public interface
|
||||
*--------------------------------------------------------------*/
|
||||
struct bcache *bcache_create(sector_t block_sectors, unsigned nr_cache_blocks)
|
||||
struct bcache *bcache_create(sector_t block_sectors, unsigned nr_cache_blocks,
|
||||
struct io_engine *engine)
|
||||
{
|
||||
struct bcache *cache;
|
||||
unsigned max_io = engine->max_io(engine);
|
||||
|
||||
if (!nr_cache_blocks) {
|
||||
log_warn("bcache must have at least one cache block");
|
||||
@ -766,13 +781,8 @@ struct bcache *bcache_create(sector_t block_sectors, unsigned nr_cache_blocks)
|
||||
|
||||
cache->block_sectors = block_sectors;
|
||||
cache->nr_cache_blocks = nr_cache_blocks;
|
||||
cache->max_io = nr_cache_blocks < MAX_IO ? nr_cache_blocks : MAX_IO;
|
||||
cache->engine = _engine_create(cache->max_io);
|
||||
if (!cache->engine) {
|
||||
dm_free(cache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cache->max_io = nr_cache_blocks < max_io ? nr_cache_blocks : max_io;
|
||||
cache->engine = engine;
|
||||
cache->nr_locked = 0;
|
||||
cache->nr_dirty = 0;
|
||||
cache->nr_io_pending = 0;
|
||||
@ -784,7 +794,7 @@ struct bcache *bcache_create(sector_t block_sectors, unsigned nr_cache_blocks)
|
||||
dm_list_init(&cache->io_pending);
|
||||
|
||||
if (!_hash_table_init(cache, nr_cache_blocks)) {
|
||||
_engine_destroy(cache->engine);
|
||||
cache->engine->destroy(cache->engine);
|
||||
dm_free(cache);
|
||||
return NULL;
|
||||
}
|
||||
@ -797,7 +807,7 @@ struct bcache *bcache_create(sector_t block_sectors, unsigned nr_cache_blocks)
|
||||
cache->prefetches = 0;
|
||||
|
||||
if (!_init_free_list(cache, nr_cache_blocks)) {
|
||||
_engine_destroy(cache->engine);
|
||||
cache->engine->destroy(cache->engine);
|
||||
_hash_table_exit(cache);
|
||||
dm_free(cache);
|
||||
return NULL;
|
||||
@ -815,7 +825,7 @@ void bcache_destroy(struct bcache *cache)
|
||||
_wait_all(cache);
|
||||
_exit_free_list(cache);
|
||||
_hash_table_exit(cache);
|
||||
_engine_destroy(cache->engine);
|
||||
cache->engine->destroy(cache->engine);
|
||||
dm_free(cache);
|
||||
}
|
||||
|
||||
@ -834,10 +844,12 @@ void bcache_prefetch(struct bcache *cache, int fd, block_address index)
|
||||
struct block *b = _hash_lookup(cache, fd, index);
|
||||
|
||||
if (!b) {
|
||||
b = _new_block(cache, fd, index);
|
||||
if (b && (cache->nr_io_pending < cache->max_io)) {
|
||||
cache->prefetches++;
|
||||
_issue_read(b);
|
||||
if (cache->nr_io_pending < cache->max_io) {
|
||||
b = _new_block(cache, fd, index, false);
|
||||
if (b) {
|
||||
cache->prefetches++;
|
||||
_issue_read(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -881,9 +893,10 @@ int bcache_flush(struct bcache *cache)
|
||||
{
|
||||
while (!dm_list_empty(&cache->dirty)) {
|
||||
struct block *b = dm_list_item(_list_pop(&cache->dirty), struct block);
|
||||
if (b->ref_count || _test_flags(b, BF_IO_PENDING))
|
||||
if (b->ref_count || _test_flags(b, BF_IO_PENDING)) {
|
||||
// The superblock may well be still locked.
|
||||
continue;
|
||||
}
|
||||
|
||||
_issue_write(b);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#ifndef BCACHE_H
|
||||
#define BCACHE_H
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -22,9 +23,34 @@
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
// FIXME: move somewhere more sensible
|
||||
#define container_of(v, t, head) \
|
||||
((t *)((const char *)(v) - (const char *)&((t *) 0)->head))
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
enum dir {
|
||||
DIR_READ,
|
||||
DIR_WRITE
|
||||
};
|
||||
|
||||
typedef uint64_t block_address;
|
||||
typedef uint64_t sector_t;
|
||||
|
||||
typedef void io_complete_fn(void *context, int io_error);
|
||||
|
||||
struct io_engine {
|
||||
void (*destroy)(struct io_engine *e);
|
||||
bool (*issue)(struct io_engine *e, enum dir d, int fd,
|
||||
sector_t sb, sector_t se, void *data, void *context);
|
||||
bool (*wait)(struct io_engine *e, io_complete_fn fn);
|
||||
unsigned (*max_io)(struct io_engine *e);
|
||||
};
|
||||
|
||||
struct io_engine *create_async_io_engine(unsigned max_io);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
struct bcache;
|
||||
struct block {
|
||||
/* clients may only access these three fields */
|
||||
@ -41,7 +67,11 @@ struct block {
|
||||
int error;
|
||||
};
|
||||
|
||||
struct bcache *bcache_create(sector_t block_size, unsigned nr_cache_blocks);
|
||||
/*
|
||||
* Ownership of engine passes. Engine will be destroyed even if this fails.
|
||||
*/
|
||||
struct bcache *bcache_create(sector_t block_size, unsigned nr_cache_blocks,
|
||||
struct io_engine *engine);
|
||||
void bcache_destroy(struct bcache *cache);
|
||||
|
||||
enum bcache_get_flags {
|
||||
|
@ -12,22 +12,28 @@
|
||||
|
||||
UNIT_SOURCE=\
|
||||
test/unit/bcache_t.c \
|
||||
test/unit/bitset_t.c\
|
||||
test/unit/config_t.c\
|
||||
test/unit/dmlist_t.c\
|
||||
test/unit/dmstatus_t.c\
|
||||
test/unit/matcher_t.c\
|
||||
test/unit/percent_t.c\
|
||||
test/unit/string_t.c\
|
||||
test/unit/run.c
|
||||
|
||||
|
||||
# test/unit/run.c
|
||||
|
||||
# test/unit/bitset_t.c\
|
||||
# test/unit/config_t.c\
|
||||
# test/unit/dmlist_t.c\
|
||||
# test/unit/dmstatus_t.c\
|
||||
# test/unit/matcher_t.c\
|
||||
# test/unit/percent_t.c\
|
||||
# test/unit/string_t.c\
|
||||
|
||||
UNIT_OBJECTS=$(UNIT_SOURCE:%.c=%.o)
|
||||
|
||||
UNIT_LDLIBS += $(LVMINTERNAL_LIBS) -ldevmapper -laio -lcunit
|
||||
UNIT_LDLIBS += $(LVMINTERNAL_LIBS) -ldevmapper -laio
|
||||
|
||||
test/unit/run: $(UNIT_OBJECTS) libdm/libdevmapper.$(LIB_SUFFIX) lib/liblvm-internal.a
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) -L$(top_builddir)/libdm \
|
||||
@echo " [LD] $@"
|
||||
$(Q) $(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) -L$(top_builddir)/libdm \
|
||||
-o $@ $(UNIT_OBJECTS) $(UNIT_LDLIBS)
|
||||
|
||||
.PHONEY: unit-test
|
||||
unit-test: test/unit/run
|
||||
@echo Running unit tests
|
||||
LD_LIBRARY_PATH=libdm test/unit/run
|
||||
|
@ -12,168 +12,540 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "units.h"
|
||||
#include "bcache.h"
|
||||
|
||||
#define SHOW_MOCK_CALLS 0
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Assertions
|
||||
*--------------------------------------------------------------*/
|
||||
|
||||
static jmp_buf _test_k;
|
||||
#define TEST_FAILED 1
|
||||
|
||||
static void _fail(const char *fmt, ...)
|
||||
__attribute__((format (printf, 1, 2)));
|
||||
|
||||
|
||||
static void _fail(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
longjmp(_test_k, TEST_FAILED);
|
||||
}
|
||||
|
||||
#define T_ASSERT(e) if (!(e)) {_fail("assertion failed: '%s'", # e);}
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Mock engine
|
||||
*--------------------------------------------------------------*/
|
||||
struct mock_engine {
|
||||
struct io_engine e;
|
||||
struct dm_list expected_calls;
|
||||
struct dm_list issued_io;
|
||||
unsigned max_io;
|
||||
};
|
||||
|
||||
enum method {
|
||||
E_DESTROY,
|
||||
E_ISSUE,
|
||||
E_WAIT,
|
||||
E_MAX_IO
|
||||
};
|
||||
|
||||
struct mock_call {
|
||||
struct dm_list list;
|
||||
enum method m;
|
||||
};
|
||||
|
||||
struct mock_io {
|
||||
struct dm_list list;
|
||||
int fd;
|
||||
sector_t sb;
|
||||
sector_t se;
|
||||
void *data;
|
||||
void *context;
|
||||
};
|
||||
|
||||
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));
|
||||
mc->m = m;
|
||||
dm_list_add(&e->expected_calls, &mc->list);
|
||||
}
|
||||
|
||||
static void _expect_read(struct mock_engine *e)
|
||||
{
|
||||
// FIXME: finish
|
||||
_expect(e, E_ISSUE);
|
||||
}
|
||||
|
||||
static void _expect_write(struct mock_engine *e)
|
||||
{
|
||||
// FIXME: finish
|
||||
_expect(e, E_ISSUE);
|
||||
}
|
||||
|
||||
static void _match(struct mock_engine *e, enum method m)
|
||||
{
|
||||
struct mock_call *mc;
|
||||
|
||||
if (dm_list_empty(&e->expected_calls))
|
||||
_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)
|
||||
_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
|
||||
|
||||
free(mc);
|
||||
}
|
||||
|
||||
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 fd,
|
||||
sector_t sb, sector_t se, void *data, void *context)
|
||||
{
|
||||
struct mock_io *io;
|
||||
struct mock_engine *me = _to_mock(e);
|
||||
|
||||
_match(me, E_ISSUE);
|
||||
io = malloc(sizeof(*io));
|
||||
if (!io)
|
||||
abort();
|
||||
|
||||
io->fd = fd;
|
||||
io->sb = sb;
|
||||
io->se = se;
|
||||
io->data = data;
|
||||
io->context = context;
|
||||
|
||||
dm_list_add(&me->issued_io, &io->list);
|
||||
return true;
|
||||
}
|
||||
|
||||
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, 0);
|
||||
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)
|
||||
{
|
||||
struct mock_engine *m = malloc(sizeof(*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;
|
||||
dm_list_init(&m->expected_calls);
|
||||
dm_list_init(&m->issued_io);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Tests
|
||||
*--------------------------------------------------------------*/
|
||||
#define MEG 2048
|
||||
#define SECTOR_SHIFT 9
|
||||
|
||||
static const char *_test_path = "test.bin";
|
||||
|
||||
int bcache_init(void)
|
||||
static void good_create(sector_t block_size, unsigned nr_cache_blocks)
|
||||
{
|
||||
return 0;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
int bcache_fini(void)
|
||||
static void bad_create(sector_t block_size, unsigned nr_cache_blocks)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
static int open_file(const char *path)
|
||||
{
|
||||
return open(path, O_EXCL | O_RDWR | O_DIRECT, 0666);
|
||||
}
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(block_size, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(!cache);
|
||||
|
||||
static int _prep_file(const char *path)
|
||||
{
|
||||
int fd, r;
|
||||
|
||||
fd = open(path, O_CREAT | O_TRUNC | O_EXCL | O_RDWR | O_DIRECT, 0666);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
r = fallocate(fd, FALLOC_FL_ZERO_RANGE, 0, (1 * MEG) << SECTOR_SHIFT);
|
||||
if (r) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int test_init(void)
|
||||
{
|
||||
unlink(_test_path);
|
||||
return _prep_file(_test_path);
|
||||
}
|
||||
|
||||
static int test_exit(void)
|
||||
{
|
||||
unlink(_test_path);
|
||||
return 0;
|
||||
_expect(me, E_DESTROY);
|
||||
me->e.destroy(&me->e);
|
||||
}
|
||||
|
||||
static void test_create(void)
|
||||
{
|
||||
struct bcache *cache = bcache_create(8, 16);
|
||||
CU_ASSERT_PTR_NOT_NULL(cache);
|
||||
bcache_destroy(cache);
|
||||
good_create(8, 16);
|
||||
}
|
||||
|
||||
static void test_nr_cache_blocks_must_be_positive(void)
|
||||
{
|
||||
struct bcache *cache = bcache_create(8, 0);
|
||||
CU_ASSERT_PTR_NULL(cache);
|
||||
bad_create(8, 0);
|
||||
}
|
||||
|
||||
static void test_block_size_must_be_positive(void)
|
||||
{
|
||||
struct bcache *cache = bcache_create(0, 16);
|
||||
CU_ASSERT_PTR_NULL(cache);
|
||||
bad_create(0, 16);
|
||||
}
|
||||
|
||||
static void test_block_size_must_be_multiple_of_page_size(void)
|
||||
{
|
||||
static 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 < 1000; i++)
|
||||
good_create(i * 8, 16);
|
||||
}
|
||||
|
||||
static void test_get_triggers_read(void)
|
||||
{
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, 16, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
static unsigned _bad_examples[] = {3, 9, 13, 1025};
|
||||
|
||||
for (i = 0; i < DM_ARRAY_SIZE(_bad_examples); i++) {
|
||||
cache = bcache_create(_bad_examples[i], 16);
|
||||
CU_ASSERT_PTR_NULL(cache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Only testing a few sizes because io_destroy is seriously
|
||||
// slow.
|
||||
for (i = 1; i < 25; i++) {
|
||||
cache = bcache_create(8 * i, 16);
|
||||
CU_ASSERT_PTR_NOT_NULL(cache);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_reads_work(void)
|
||||
{
|
||||
int fd;
|
||||
|
||||
// FIXME: add fixtures.
|
||||
test_init();
|
||||
fd = open_file("./test.bin");
|
||||
CU_ASSERT(fd >= 0);
|
||||
|
||||
{
|
||||
int i;
|
||||
int fd = 17; // arbitrary key
|
||||
struct block *b;
|
||||
struct bcache *cache = bcache_create(8, 16);
|
||||
|
||||
CU_ASSERT(bcache_get(cache, fd, 0, 0, &b));
|
||||
for (i = 0; i < 8 << SECTOR_SHIFT; i++)
|
||||
CU_ASSERT(((unsigned char *) b->data)[i] == 0);
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, 0, 0, &b));
|
||||
bcache_put(b);
|
||||
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
test_exit();
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_prefetch_works(void)
|
||||
static void test_repeated_reads_are_cached(void)
|
||||
{
|
||||
int fd;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: add fixtures.
|
||||
test_init();
|
||||
fd = open_file("./test.bin");
|
||||
CU_ASSERT(fd >= 0);
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, 16, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int i;
|
||||
int fd = 17; // arbitrary key
|
||||
unsigned i;
|
||||
struct block *b;
|
||||
struct bcache *cache = bcache_create(8, 16);
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
bcache_prefetch(cache, fd, i);
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
for (i = 0; i < 100; i++) {
|
||||
T_ASSERT(bcache_get(cache, fd, 0, 0, &b));
|
||||
bcache_put(b);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
CU_ASSERT(bcache_get(cache, fd, i, 0, &b));
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_block_gets_evicted_with_many_reads(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
unsigned i;
|
||||
struct block *b;
|
||||
|
||||
for (i = 0; i < nr_cache_blocks; i++) {
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, i, 0, &b));
|
||||
bcache_put(b);
|
||||
}
|
||||
|
||||
bcache_destroy(cache);
|
||||
// Not enough cache blocks to hold this one
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, 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(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
for (i = nr_cache_blocks; i; i--) {
|
||||
T_ASSERT(bcache_get(cache, fd, i - 1, 0, &b));
|
||||
bcache_put(b);
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
test_exit();
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_prefetch_issues_a_read(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
unsigned i;
|
||||
struct block *b;
|
||||
|
||||
for (i = 0; i < nr_cache_blocks; i++) {
|
||||
// prefetch should not wait
|
||||
_expect(me, E_ISSUE);
|
||||
bcache_prefetch(cache, fd, i);
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < nr_cache_blocks; i++) {
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, i, 0, &b));
|
||||
bcache_put(b);
|
||||
}
|
||||
}
|
||||
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_too_many_prefetches_does_not_trigger_a_wait(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < 10 * nr_cache_blocks; i++) {
|
||||
// prefetch should not wait
|
||||
if (i < nr_cache_blocks)
|
||||
_expect(me, E_ISSUE);
|
||||
bcache_prefetch(cache, fd, i);
|
||||
}
|
||||
|
||||
// Destroy will wait for any in flight IO triggered by prefetches.
|
||||
for (i = 0; i < nr_cache_blocks; i++)
|
||||
_expect(me, E_WAIT);
|
||||
}
|
||||
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_dirty_data_gets_written_back(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
struct block *b;
|
||||
|
||||
// FIXME: be specific about the IO direction
|
||||
// Expect the read
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, 0, GF_DIRTY, &b));
|
||||
bcache_put(b);
|
||||
|
||||
// Expect the write
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
}
|
||||
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_zeroed_data_counts_as_dirty(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
struct block *b;
|
||||
|
||||
// No read
|
||||
T_ASSERT(bcache_get(cache, fd, 0, GF_ZERO, &b));
|
||||
bcache_put(b);
|
||||
|
||||
// Expect the write
|
||||
_expect(me, E_ISSUE);
|
||||
_expect(me, E_WAIT);
|
||||
}
|
||||
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
static void test_flush_waits_for_all_dirty(void)
|
||||
{
|
||||
const unsigned nr_cache_blocks = 128, count = 16;
|
||||
struct bcache *cache;
|
||||
struct mock_engine *me = _mock_create(16);
|
||||
|
||||
// FIXME: use a fixture
|
||||
_expect(me, E_MAX_IO);
|
||||
|
||||
// I'm using a large nr of cache blocks to avoid triggering writeback
|
||||
// early.
|
||||
cache = bcache_create(64, nr_cache_blocks, &me->e);
|
||||
T_ASSERT(cache);
|
||||
|
||||
{
|
||||
int fd = 17; // arbitrary key
|
||||
unsigned i;
|
||||
struct block *b;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i % 2) {
|
||||
T_ASSERT(bcache_get(cache, fd, i, GF_ZERO, &b));
|
||||
} else {
|
||||
_expect_read(me);
|
||||
_expect(me, E_WAIT);
|
||||
T_ASSERT(bcache_get(cache, fd, i, 0, &b));
|
||||
}
|
||||
bcache_put(b);
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i % 2)
|
||||
_expect_write(me);
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i % 2)
|
||||
_expect(me, E_WAIT);
|
||||
}
|
||||
|
||||
bcache_flush(cache);
|
||||
_no_outstanding_expectations(me);
|
||||
}
|
||||
|
||||
_expect(me, E_DESTROY);
|
||||
bcache_destroy(cache);
|
||||
}
|
||||
|
||||
#if 0
|
||||
#define NR_FILES 4
|
||||
static void test_read_multiple_files(void)
|
||||
{
|
||||
@ -211,21 +583,58 @@ static void test_read_multiple_files(void)
|
||||
|
||||
test_exit();
|
||||
}
|
||||
|
||||
#endif
|
||||
// Tests to be written
|
||||
// Open multiple files and prove the blocks are coming from the correct file
|
||||
// show invalidate works
|
||||
// show invalidate_fd works
|
||||
// show writeback is working
|
||||
// check zeroing
|
||||
//
|
||||
CU_TestInfo bcache_list[] = {
|
||||
{ (char*)"create", test_create },
|
||||
{ (char*)"nr cache block must be positive", test_nr_cache_blocks_must_be_positive },
|
||||
{ (char*)"block size must be positive", test_block_size_must_be_positive },
|
||||
{ (char*)"block size must be multiple of page size", test_block_size_must_be_multiple_of_page_size },
|
||||
{ (char*)"reads work", test_reads_work },
|
||||
{ (char*)"prefetch works", test_prefetch_works },
|
||||
{ (char*)"read multiple files", test_read_multiple_files },
|
||||
CU_TEST_INFO_NULL
|
||||
|
||||
struct test_details {
|
||||
const char *name;
|
||||
void (*fn)(void);
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static struct test_details _tests[] = {
|
||||
{"simple create/destroy", test_create},
|
||||
{"nr cache blocks must be positive", test_nr_cache_blocks_must_be_positive},
|
||||
{"block size must be positive", test_block_size_must_be_positive},
|
||||
{"block size must be a multiple of page size", test_block_size_must_be_multiple_of_page_size},
|
||||
{"bcache_get() triggers read", test_get_triggers_read},
|
||||
{"repeated reads are cached", test_repeated_reads_are_cached},
|
||||
{"block get evicted with many reads", test_block_gets_evicted_with_many_reads},
|
||||
{"prefetch issues a read", test_prefetch_issues_a_read},
|
||||
{"too many prefetches does not trigger a wait", test_too_many_prefetches_does_not_trigger_a_wait},
|
||||
{"dirty data gets written back", test_dirty_data_gets_written_back},
|
||||
{"zeroed data counts as dirty", test_zeroed_data_counts_as_dirty},
|
||||
{"flush waits for all dirty", test_flush_waits_for_all_dirty},
|
||||
};
|
||||
|
||||
// We have to declare these as volatile because of the setjmp()
|
||||
volatile unsigned i = 0, passed = 0;
|
||||
|
||||
for (i = 0; i < DM_ARRAY_SIZE(_tests); i++) {
|
||||
struct test_details *t = _tests + i;
|
||||
fprintf(stderr, "[RUN ] %s\n", t->name);
|
||||
|
||||
if (setjmp(_test_k))
|
||||
fprintf(stderr, "[ FAIL] %s\n", t->name);
|
||||
else {
|
||||
t->fn();
|
||||
passed++;
|
||||
fprintf(stderr, "[ OK] %s\n", t->name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n%u/%lu tests passed\n", passed, DM_ARRAY_SIZE(_tests));
|
||||
|
||||
#if 0
|
||||
test_prefetch_works();
|
||||
test_read_multiple_files();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user