master - [device/bcache] bcache_read_bytes should put blocks
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=93fc9374294aae679fa...
Commit: 93fc9374294aae679faa23aef0bd631db2e9a31a
Parent: 7be54bd687ead6aa21e04c8a85a648da369a3d88
Author: David Teigland <teigland(a)redhat.com>
AuthorDate: Thu Feb 8 13:44:54 2018 -0600
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] bcache_read_bytes should put blocks
---
lib/device/bcache.c | 11 ++++++++---
1 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index 52be947..5141083 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -1009,14 +1009,17 @@ bool bcache_read_bytes(struct bcache *cache, int fd, off_t start, size_t len, vo
block_address bb, be, i;
unsigned char *udata = data;
off_t block_size = cache->block_sectors << SECTOR_SHIFT;
+ int errors = 0;
byte_range_to_block_range(cache, start, len, &bb, &be);
for (i = bb; i < be; i++)
bcache_prefetch(cache, fd, i);
for (i = bb; i < be; i++) {
- if (!bcache_get(cache, fd, i, 0, &b))
- return false;
+ if (!bcache_get(cache, fd, i, 0, &b)) {
+ errors++;
+ continue;
+ }
if (i == bb) {
off_t block_offset = start % block_size;
@@ -1030,9 +1033,11 @@ bool bcache_read_bytes(struct bcache *cache, int fd, off_t start, size_t len, vo
len -= blen;
udata += blen;
}
+
+ bcache_put(b);
}
- return true;
+ return errors ? false : true;
}
//----------------------------------------------------------------
6 years, 1 month
master - [device/bcache] fix min() function
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=7be54bd687ead6aa21e...
Commit: 7be54bd687ead6aa21e04c8a85a648da369a3d88
Parent: d9e6298edb0bc6533c22f7e95e613189abe89c99
Author: David Teigland <teigland(a)redhat.com>
AuthorDate: Thu Feb 8 11:16:19 2018 -0600
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] fix min() function
---
lib/device/bcache.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index cea4db4..52be947 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -997,7 +997,7 @@ void bcache_prefetch_bytes(struct bcache *cache, int fd, off_t start, size_t len
static off_t _min(off_t lhs, off_t rhs)
{
- if (rhs > lhs)
+ if (rhs < lhs)
return rhs;
return lhs;
6 years, 1 month
master - [device/bcache] fix missing max_io fn in bcache async engine
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=d9e6298edb0bc6533c2...
Commit: d9e6298edb0bc6533c22f7e95e613189abe89c99
Parent: dc8034f5eb8d14b621c3d99ff58c95f74153c448
Author: David Teigland <teigland(a)redhat.com>
AuthorDate: Thu Feb 8 10:10:31 2018 -0600
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] fix missing max_io fn in bcache async engine
---
lib/device/bcache.c | 10 ++++++++++
1 files changed, 10 insertions(+), 0 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index dce05ef..cea4db4 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -134,6 +134,7 @@ struct async_engine {
struct io_engine e;
io_context_t aio_context;
struct cb_set *cbs;
+ unsigned max_io;
};
static struct async_engine *_to_async(struct io_engine *e)
@@ -233,6 +234,12 @@ static bool _async_wait(struct io_engine *ioe, io_complete_fn fn)
return true;
}
+static unsigned _async_max_io(struct io_engine *ioe)
+{
+ struct async_engine *e = _to_async(ioe);
+ return e->max_io;
+}
+
struct io_engine *create_async_io_engine(unsigned max_io)
{
int r;
@@ -241,9 +248,12 @@ struct io_engine *create_async_io_engine(unsigned max_io)
if (!e)
return NULL;
+ e->max_io = max_io;
+
e->e.destroy = _async_destroy;
e->e.issue = _async_issue;
e->e.wait = _async_wait;
+ e->e.max_io = _async_max_io;
e->aio_context = 0;
r = io_setup(max_io, &e->aio_context);
6 years, 1 month
master - [device/bcache] more work on bcache
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=dc8034f5eb8d14b621c...
Commit: dc8034f5eb8d14b621c3d99ff58c95f74153c448
Parent: 1cde30eba0fc4404f092fb5106f7cc7fcd66795b
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Tue Feb 6 15:10:44 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] more work on bcache
---
lib/device/bcache.c | 2 +-
test/unit/bcache_t.c | 152 +++++++++++++++++++++++++++++++++----------------
2 files changed, 103 insertions(+), 51 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index 1d83306..dce05ef 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -365,7 +365,7 @@ static struct block *_hash_lookup(struct bcache *cache, int fd, uint64_t index)
unsigned h = _hash(cache, fd, index);
dm_list_iterate_items_gen (b, cache->buckets + h, hash)
- if (b->index == index)
+ if (b->fd == fd && b->index == index)
return b;
return NULL;
diff --git a/test/unit/bcache_t.c b/test/unit/bcache_t.c
index 5fb6789..07a45fe 100644
--- a/test/unit/bcache_t.c
+++ b/test/unit/bcache_t.c
@@ -54,6 +54,7 @@ struct mock_engine {
struct dm_list expected_calls;
struct dm_list issued_io;
unsigned max_io;
+ sector_t block_size;
};
enum method {
@@ -66,6 +67,11 @@ enum method {
struct mock_call {
struct dm_list list;
enum method m;
+
+ bool match_args;
+ enum dir d;
+ int fd;
+ block_address b;
};
struct mock_io {
@@ -97,23 +103,35 @@ static void _expect(struct mock_engine *e, enum method m)
{
struct mock_call *mc = malloc(sizeof(*mc));
mc->m = m;
+ mc->match_args = false;
dm_list_add(&e->expected_calls, &mc->list);
}
-static void _expect_read(struct mock_engine *e)
+static void _expect_read(struct mock_engine *e, int fd, block_address b)
{
- // FIXME: finish
- _expect(e, E_ISSUE);
+ struct mock_call *mc = malloc(sizeof(*mc));
+ mc->m = E_ISSUE;
+ mc->match_args = true;
+ mc->d = DIR_READ;
+ mc->fd = fd;
+ mc->b = b;
+ dm_list_add(&e->expected_calls, &mc->list);
}
-static void _expect_write(struct mock_engine *e)
+static void _expect_write(struct mock_engine *e, int fd, block_address b)
{
- // FIXME: finish
- _expect(e, E_ISSUE);
+ struct mock_call *mc = malloc(sizeof(*mc));
+ mc->m = E_ISSUE;
+ mc->match_args = true;
+ mc->d = DIR_WRITE;
+ mc->fd = fd;
+ mc->b = b;
+ dm_list_add(&e->expected_calls, &mc->list);
}
-static void _match(struct mock_engine *e, enum method m)
+static struct mock_call *_match_pop(struct mock_engine *e, enum method m)
{
+
struct mock_call *mc;
if (dm_list_empty(&e->expected_calls))
@@ -129,7 +147,12 @@ static void _match(struct mock_engine *e, enum method m)
fprintf(stderr, "%s called (expected)\n", _show_method(m));
#endif
- free(mc);
+ 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)
@@ -163,9 +186,18 @@ 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_call *mc;
struct mock_engine *me = _to_mock(e);
- _match(me, E_ISSUE);
+ mc = _match_pop(me, E_ISSUE);
+ if (mc->match_args) {
+ T_ASSERT(d == mc->d);
+ T_ASSERT(fd == mc->fd);
+ T_ASSERT(sb == mc->b * me->block_size);
+ T_ASSERT(se == (mc->b + 1) * me->block_size);
+ }
+ free(mc);
+
io = malloc(sizeof(*io));
if (!io)
abort();
@@ -202,7 +234,7 @@ static unsigned _mock_max_io(struct io_engine *e)
return me->max_io;
}
-static struct mock_engine *_mock_create(unsigned max_io)
+static struct mock_engine *_mock_create(unsigned max_io, sector_t block_size)
{
struct mock_engine *m = malloc(sizeof(*m));
@@ -212,6 +244,7 @@ static struct mock_engine *_mock_create(unsigned max_io)
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);
@@ -226,15 +259,15 @@ struct fixture {
struct bcache *cache;
};
-static struct fixture *_fixture_init(unsigned nr_cache_blocks)
+static struct fixture *_fixture_init(sector_t block_size, unsigned nr_cache_blocks)
{
struct fixture *f = malloc(sizeof(*f));
- f->me = _mock_create(16);
+ f->me = _mock_create(16, block_size);
T_ASSERT(f->me);
_expect(f->me, E_MAX_IO);
- f->cache = bcache_create(128, nr_cache_blocks, &f->me->e);
+ f->cache = bcache_create(block_size, nr_cache_blocks, &f->me->e);
T_ASSERT(f->cache);
return f;
@@ -250,7 +283,7 @@ static void _fixture_exit(struct fixture *f)
static void *_small_fixture_init(void)
{
- return _fixture_init(16);
+ return _fixture_init(128, 16);
}
static void _small_fixture_exit(void *context)
@@ -260,7 +293,7 @@ static void _small_fixture_exit(void *context)
static void *_large_fixture_init(void)
{
- return _fixture_init(1024);
+ return _fixture_init(128, 1024);
}
static void _large_fixture_exit(void *context)
@@ -277,7 +310,7 @@ static void _large_fixture_exit(void *context)
static void good_create(sector_t block_size, unsigned nr_cache_blocks)
{
struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct mock_engine *me = _mock_create(16, 128);
_expect(me, E_MAX_IO);
cache = bcache_create(block_size, nr_cache_blocks, &me->e);
@@ -290,7 +323,7 @@ static void good_create(sector_t block_size, unsigned nr_cache_blocks)
static void bad_create(sector_t block_size, unsigned nr_cache_blocks)
{
struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct mock_engine *me = _mock_create(16, 128);
_expect(me, E_MAX_IO);
cache = bcache_create(block_size, nr_cache_blocks, &me->e);
@@ -335,7 +368,7 @@ static void test_get_triggers_read(void *context)
int fd = 17; // arbitrary key
struct block *b;
- _expect(f->me, E_ISSUE);
+ _expect_read(f->me, fd, 0);
_expect(f->me, E_WAIT);
T_ASSERT(bcache_get(f->cache, fd, 0, 0, &b));
bcache_put(b);
@@ -349,7 +382,7 @@ static void test_repeated_reads_are_cached(void *context)
unsigned i;
struct block *b;
- _expect(f->me, E_ISSUE);
+ _expect_read(f->me, fd, 0);
_expect(f->me, E_WAIT);
for (i = 0; i < 100; i++) {
T_ASSERT(bcache_get(f->cache, fd, 0, 0, &b));
@@ -370,14 +403,14 @@ static void test_block_gets_evicted_with_many_reads(void *context)
struct block *b;
for (i = 0; i < nr_cache_blocks; i++) {
- _expect(me, E_ISSUE);
+ _expect_read(me, fd, i);
_expect(me, E_WAIT);
T_ASSERT(bcache_get(cache, fd, i, 0, &b));
bcache_put(b);
}
// Not enough cache blocks to hold this one
- _expect(me, E_ISSUE);
+ _expect_read(me, fd, nr_cache_blocks);
_expect(me, E_WAIT);
T_ASSERT(bcache_get(cache, fd, nr_cache_blocks, 0, &b));
bcache_put(b);
@@ -406,7 +439,7 @@ static void test_prefetch_issues_a_read(void *context)
for (i = 0; i < nr_cache_blocks; i++) {
// prefetch should not wait
- _expect(me, E_ISSUE);
+ _expect_read(me, fd, i);
bcache_prefetch(cache, fd, i);
}
@@ -431,7 +464,7 @@ static void test_too_many_prefetches_does_not_trigger_a_wait(void *context)
for (i = 0; i < 10 * nr_cache_blocks; i++) {
// prefetch should not wait
if (i < nr_cache_blocks)
- _expect(me, E_ISSUE);
+ _expect_read(me, fd, i);
bcache_prefetch(cache, fd, i);
}
@@ -446,19 +479,17 @@ static void test_dirty_data_gets_written_back(void *context)
struct mock_engine *me = f->me;
struct bcache *cache = f->cache;
- const unsigned nr_cache_blocks = 16;
int fd = 17; // arbitrary key
struct block *b;
- // FIXME: be specific about the IO direction
// Expect the read
- _expect(me, E_ISSUE);
+ _expect_read(me, fd, 0);
_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_write(me, fd, 0);
_expect(me, E_WAIT);
}
@@ -468,7 +499,6 @@ static void test_zeroed_data_counts_as_dirty(void *context)
struct mock_engine *me = f->me;
struct bcache *cache = f->cache;
- const unsigned nr_cache_blocks = 16;
int fd = 17; // arbitrary key
struct block *b;
@@ -477,7 +507,7 @@ static void test_zeroed_data_counts_as_dirty(void *context)
bcache_put(b);
// Expect the write
- _expect(me, E_ISSUE);
+ _expect_write(me, fd, 0);
_expect(me, E_WAIT);
}
@@ -496,7 +526,7 @@ static void test_flush_waits_for_all_dirty(void *context)
if (i % 2) {
T_ASSERT(bcache_get(cache, fd, i, GF_ZERO, &b));
} else {
- _expect_read(me);
+ _expect_read(me, fd, i);
_expect(me, E_WAIT);
T_ASSERT(bcache_get(cache, fd, i, 0, &b));
}
@@ -505,7 +535,7 @@ static void test_flush_waits_for_all_dirty(void *context)
for (i = 0; i < count; i++) {
if (i % 2)
- _expect_write(me);
+ _expect_write(me, fd, i);
}
for (i = 0; i < count; i++) {
@@ -517,6 +547,25 @@ static void test_flush_waits_for_all_dirty(void *context)
_no_outstanding_expectations(me);
}
+static void test_multiple_files(void * context)
+{
+ static int _fds[] = {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(_fds); i++) {
+ _expect_read(me, _fds[i], 0);
+ _expect(me, E_WAIT);
+
+ T_ASSERT(bcache_get(cache, _fds[i], 0, 0, &b));
+ bcache_put(b);
+ }
+}
+
// Tests to be written
// Open multiple files and prove the blocks are coming from the correct file
// show invalidate works
@@ -525,31 +574,34 @@ static void test_flush_waits_for_all_dirty(void *context)
// check zeroing
struct test_details {
- const char *name;
+ const char *path;
+ const char *desc;
void (*fn)(void *);
void *(*fixture_init)(void);
void (*fixture_exit)(void *);
};
-#define TEST(name, fn) {name, fn, NULL, NULL}
-#define TEST_S(name, fn) {name, fn, _small_fixture_init, _small_fixture_exit}
-#define TEST_L(name, fn) {name, fn, _large_fixture_init, _large_fixture_exit}
+#define PATH "device/bcache/"
+#define TEST(path, name, fn) {PATH path, name, fn, NULL, NULL}
+#define TEST_S(path, name, fn) {PATH path, name, fn, _small_fixture_init, _small_fixture_exit}
+#define TEST_L(path, name, fn) {PATH path, name, fn, _large_fixture_init, _large_fixture_exit}
int main(int argc, char **argv)
{
static struct test_details _tests[] = {
- TEST("simple create/destroy", test_create),
- TEST("nr cache blocks must be positive", test_nr_cache_blocks_must_be_positive),
- TEST("block size must be positive", test_block_size_must_be_positive),
- TEST("block size must be a multiple of page size", test_block_size_must_be_multiple_of_page_size),
- TEST_S("bcache_get() triggers read", test_get_triggers_read),
- TEST_S("repeated reads are cached", test_repeated_reads_are_cached),
- TEST_S("block get evicted with many reads", test_block_gets_evicted_with_many_reads),
- TEST_S("prefetch issues a read", test_prefetch_issues_a_read),
- TEST_S("too many prefetches does not trigger a wait", test_too_many_prefetches_does_not_trigger_a_wait),
- TEST_S("dirty data gets written back", test_dirty_data_gets_written_back),
- TEST_S("zeroed data counts as dirty", test_zeroed_data_counts_as_dirty),
- TEST_L("flush waits for all dirty", test_flush_waits_for_all_dirty),
+ TEST("create-destroy", "simple create/destroy", test_create),
+ TEST("cache-blocks-positive", "nr cache blocks must be positive", test_nr_cache_blocks_must_be_positive),
+ TEST("block-size-positive", "block size must be positive", test_block_size_must_be_positive),
+ TEST("block-size-multiple-page", "block size must be a multiple of page size", test_block_size_must_be_multiple_of_page_size),
+ TEST_S("get-reads", "bcache_get() triggers read", test_get_triggers_read),
+ TEST_S("reads-cached", "repeated reads are cached", test_repeated_reads_are_cached),
+ TEST_S("blocks-get-evicted", "block get evicted with many reads", test_block_gets_evicted_with_many_reads),
+ TEST_S("prefetch-reads", "prefetch issues a read", test_prefetch_issues_a_read),
+ TEST_S("prefetch-never-waits", "too many prefetches does not trigger a wait", test_too_many_prefetches_does_not_trigger_a_wait),
+ TEST_S("writeback-occurs", "dirty data gets written back", test_dirty_data_gets_written_back),
+ TEST_S("zero-flag-dirties", "zeroed data counts as dirty", test_zeroed_data_counts_as_dirty),
+ TEST_L("flush waits for all dirty", "flush waits for all dirty", test_flush_waits_for_all_dirty),
+ TEST_S("read-multiple-files", "read from multiple files", test_multiple_files),
};
// We have to declare these as volatile because of the setjmp()
@@ -558,10 +610,10 @@ int main(int argc, char **argv)
for (i = 0; i < DM_ARRAY_SIZE(_tests); i++) {
void *fixture;
struct test_details *t = _tests + i;
- fprintf(stderr, "[RUN ] %s\n", t->name);
+ fprintf(stderr, "[RUN ] %s\n", t->path);
if (setjmp(_test_k))
- fprintf(stderr, "[ FAIL] %s\n", t->name);
+ fprintf(stderr, "[ FAIL] %s\n", t->path);
else {
if (t->fixture_init)
fixture = t->fixture_init();
@@ -574,7 +626,7 @@ int main(int argc, char **argv)
t->fixture_exit(fixture);
passed++;
- fprintf(stderr, "[ OK] %s\n", t->name);
+ fprintf(stderr, "[ OK] %s\n", t->path);
}
}
6 years, 1 month
master - [device/bcache] More fiddling with tests
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=1cde30eba0fc4404f09...
Commit: 1cde30eba0fc4404f092fb5106f7cc7fcd66795b
Parent: 6a57ed17a28aee0e8fb9557ec3c03a02f0b2a4be
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Tue Feb 6 13:06:15 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] More fiddling with tests
---
test/unit/bcache_t.c | 484 ++++++++++++++++++++++----------------------------
1 files changed, 214 insertions(+), 270 deletions(-)
diff --git a/test/unit/bcache_t.c b/test/unit/bcache_t.c
index c2d2df0..5fb6789 100644
--- a/test/unit/bcache_t.c
+++ b/test/unit/bcache_t.c
@@ -219,6 +219,56 @@ static struct mock_engine *_mock_create(unsigned max_io)
}
/*----------------------------------------------------------------
+ * Fixtures
+ *--------------------------------------------------------------*/
+struct fixture {
+ struct mock_engine *me;
+ struct bcache *cache;
+};
+
+static struct fixture *_fixture_init(unsigned nr_cache_blocks)
+{
+ struct fixture *f = malloc(sizeof(*f));
+
+ f->me = _mock_create(16);
+ T_ASSERT(f->me);
+
+ _expect(f->me, E_MAX_IO);
+ f->cache = bcache_create(128, nr_cache_blocks, &f->me->e);
+ T_ASSERT(f->cache);
+
+ return f;
+}
+
+static void _fixture_exit(struct fixture *f)
+{
+ _expect(f->me, E_DESTROY);
+ bcache_destroy(f->cache);
+
+ free(f);
+}
+
+static void *_small_fixture_init(void)
+{
+ return _fixture_init(16);
+}
+
+static void _small_fixture_exit(void *context)
+{
+ _fixture_exit(context);
+}
+
+static void *_large_fixture_init(void)
+{
+ return _fixture_init(1024);
+}
+
+static void _large_fixture_exit(void *context)
+{
+ _fixture_exit(context);
+}
+
+/*----------------------------------------------------------------
* Tests
*--------------------------------------------------------------*/
#define MEG 2048
@@ -250,22 +300,22 @@ static void bad_create(sector_t block_size, unsigned nr_cache_blocks)
me->e.destroy(&me->e);
}
-static void test_create(void)
+static void test_create(void *fixture)
{
good_create(8, 16);
}
-static void test_nr_cache_blocks_must_be_positive(void)
+static void test_nr_cache_blocks_must_be_positive(void *fixture)
{
bad_create(8, 0);
}
-static void test_block_size_must_be_positive(void)
+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)
+static void test_block_size_must_be_multiple_of_page_size(void *fixture)
{
static unsigned _bad_examples[] = {3, 9, 13, 1025};
@@ -278,312 +328,195 @@ static void test_block_size_must_be_multiple_of_page_size(void)
good_create(i * 8, 16);
}
-static void test_get_triggers_read(void)
+static void test_get_triggers_read(void *context)
{
- struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct fixture *f = context;
- // FIXME: use a fixture
- _expect(me, E_MAX_IO);
- cache = bcache_create(64, 16, &me->e);
- T_ASSERT(cache);
+ int fd = 17; // arbitrary key
+ struct block *b;
- {
- int fd = 17; // arbitrary key
- struct block *b;
-
- _expect(me, E_ISSUE);
- _expect(me, E_WAIT);
- T_ASSERT(bcache_get(cache, fd, 0, 0, &b));
- bcache_put(b);
- }
-
- _expect(me, E_DESTROY);
- bcache_destroy(cache);
+ _expect(f->me, E_ISSUE);
+ _expect(f->me, E_WAIT);
+ T_ASSERT(bcache_get(f->cache, fd, 0, 0, &b));
+ bcache_put(b);
}
-static void test_repeated_reads_are_cached(void)
+static void test_repeated_reads_are_cached(void *context)
{
- struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct fixture *f = context;
- // FIXME: use a fixture
- _expect(me, E_MAX_IO);
- cache = bcache_create(64, 16, &me->e);
- T_ASSERT(cache);
-
- {
- int fd = 17; // arbitrary key
- unsigned i;
- struct block *b;
+ int fd = 17; // arbitrary key
+ unsigned i;
+ struct block *b;
- _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);
- }
+ _expect(f->me, E_ISSUE);
+ _expect(f->me, E_WAIT);
+ for (i = 0; i < 100; i++) {
+ T_ASSERT(bcache_get(f->cache, fd, 0, 0, &b));
+ bcache_put(b);
}
-
- _expect(me, E_DESTROY);
- bcache_destroy(cache);
}
-static void test_block_gets_evicted_with_many_reads(void)
+static void test_block_gets_evicted_with_many_reads(void *context)
{
- const unsigned nr_cache_blocks = 16;
- struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct fixture *f = context;
- // 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;
+ struct mock_engine *me = f->me;
+ struct bcache *cache = f->cache;
+ const unsigned nr_cache_blocks = 16;
- 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);
- }
+ int fd = 17; // arbitrary key
+ unsigned i;
+ struct block *b;
- // Not enough cache blocks to hold this one
+ for (i = 0; i < nr_cache_blocks; i++) {
_expect(me, E_ISSUE);
_expect(me, E_WAIT);
- T_ASSERT(bcache_get(cache, fd, nr_cache_blocks, 0, &b));
+ T_ASSERT(bcache_get(cache, fd, i, 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);
- }
}
- _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);
- }
+ // 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);
}
-
- _expect(me, E_DESTROY);
- bcache_destroy(cache);
}
-static void test_too_many_prefetches_does_not_trigger_a_wait(void)
+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;
- 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);
- }
+ int fd = 17; // arbitrary key
+ unsigned i;
+ struct block *b;
- // Destroy will wait for any in flight IO triggered by prefetches.
- for (i = 0; i < nr_cache_blocks; i++)
- _expect(me, E_WAIT);
+ for (i = 0; i < nr_cache_blocks; i++) {
+ // prefetch should not wait
+ _expect(me, E_ISSUE);
+ bcache_prefetch(cache, fd, i);
}
- _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);
+ for (i = 0; i < nr_cache_blocks; i++) {
_expect(me, E_WAIT);
- T_ASSERT(bcache_get(cache, fd, 0, GF_DIRTY, &b));
+ T_ASSERT(bcache_get(cache, fd, i, 0, &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)
+static void test_too_many_prefetches_does_not_trigger_a_wait(void *context)
{
- 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;
+ struct fixture *f = context;
+ struct mock_engine *me = f->me;
+ struct bcache *cache = f->cache;
- // No read
- T_ASSERT(bcache_get(cache, fd, 0, GF_ZERO, &b));
- bcache_put(b);
+ const unsigned nr_cache_blocks = 16;
+ int fd = 17; // arbitrary key
+ unsigned i;
- // Expect the write
- _expect(me, E_ISSUE);
- _expect(me, E_WAIT);
+ 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);
}
- _expect(me, E_DESTROY);
- bcache_destroy(cache);
+ // 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_flush_waits_for_all_dirty(void)
+static void test_dirty_data_gets_written_back(void *context)
{
- const unsigned nr_cache_blocks = 128, count = 16;
- struct bcache *cache;
- struct mock_engine *me = _mock_create(16);
+ struct fixture *f = context;
+ struct mock_engine *me = f->me;
+ struct bcache *cache = f->cache;
- // 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);
- }
+ const unsigned nr_cache_blocks = 16;
+ 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);
+}
- for (i = 0; i < count; i++) {
- if (i % 2)
- _expect_write(me);
- }
+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;
- for (i = 0; i < count; i++) {
- if (i % 2)
- _expect(me, E_WAIT);
- }
+ const unsigned nr_cache_blocks = 16;
+ int fd = 17; // arbitrary key
+ struct block *b;
- bcache_flush(cache);
- _no_outstanding_expectations(me);
- }
+ // No read
+ T_ASSERT(bcache_get(cache, fd, 0, GF_ZERO, &b));
+ bcache_put(b);
- _expect(me, E_DESTROY);
- bcache_destroy(cache);
+ // Expect the write
+ _expect(me, E_ISSUE);
+ _expect(me, E_WAIT);
}
-#if 0
-#define NR_FILES 4
-static void test_read_multiple_files(void)
+static void test_flush_waits_for_all_dirty(void *context)
{
- unsigned i;
- int fd[NR_FILES];
- char buffer[128];
-
-
- // FIXME: add fixtures.
- test_init();
- for (i = 0; i < NR_FILES; i++) {
- snprintf(buffer, sizeof(buffer), "./test%u.bin", i);
- unlink(buffer);
- _prep_file(buffer);
- fd[i] = open_file(buffer);
- CU_ASSERT(fd[i] >= 0);
- }
+ struct fixture *f = context;
+ struct mock_engine *me = f->me;
+ struct bcache *cache = f->cache;
- {
- struct block *b;
- struct bcache *cache = bcache_create(8, 16);
+ const unsigned count = 16;
+ int fd = 17; // arbitrary key
+ unsigned i;
+ struct block *b;
- for (i = 0; i < 64; i++) {
- if (!bcache_get(cache, fd[i % NR_FILES], i, 0, &b)) {
- CU_ASSERT(false);
- } else
- bcache_put(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);
+ }
- bcache_destroy(cache);
+ for (i = 0; i < count; i++) {
+ if (i % 2)
+ _expect_write(me);
}
- for (i = 0; i < NR_FILES; i++)
- close(fd[i]);
+ for (i = 0; i < count; i++) {
+ if (i % 2)
+ _expect(me, E_WAIT);
+ }
- test_exit();
+ bcache_flush(cache);
+ _no_outstanding_expectations(me);
}
-#endif
+
// Tests to be written
// Open multiple files and prove the blocks are coming from the correct file
// show invalidate works
@@ -593,37 +526,53 @@ static void test_read_multiple_files(void)
struct test_details {
const char *name;
- void (*fn)(void);
+ void (*fn)(void *);
+ void *(*fixture_init)(void);
+ void (*fixture_exit)(void *);
};
+#define TEST(name, fn) {name, fn, NULL, NULL}
+#define TEST_S(name, fn) {name, fn, _small_fixture_init, _small_fixture_exit}
+#define TEST_L(name, fn) {name, fn, _large_fixture_init, _large_fixture_exit}
+
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},
+ TEST("simple create/destroy", test_create),
+ TEST("nr cache blocks must be positive", test_nr_cache_blocks_must_be_positive),
+ TEST("block size must be positive", test_block_size_must_be_positive),
+ TEST("block size must be a multiple of page size", test_block_size_must_be_multiple_of_page_size),
+ TEST_S("bcache_get() triggers read", test_get_triggers_read),
+ TEST_S("repeated reads are cached", test_repeated_reads_are_cached),
+ TEST_S("block get evicted with many reads", test_block_gets_evicted_with_many_reads),
+ TEST_S("prefetch issues a read", test_prefetch_issues_a_read),
+ TEST_S("too many prefetches does not trigger a wait", test_too_many_prefetches_does_not_trigger_a_wait),
+ TEST_S("dirty data gets written back", test_dirty_data_gets_written_back),
+ TEST_S("zeroed data counts as dirty", test_zeroed_data_counts_as_dirty),
+ TEST_L("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++) {
+ void *fixture;
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();
+ if (t->fixture_init)
+ fixture = t->fixture_init();
+ else
+ fixture = NULL;
+
+ t->fn(fixture);
+
+ if (t->fixture_exit)
+ t->fixture_exit(fixture);
+
passed++;
fprintf(stderr, "[ OK] %s\n", t->name);
}
@@ -631,10 +580,5 @@ int main(int argc, char **argv)
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;
}
6 years, 1 month
master - [device/bcache] add bcache_prefetch_bytes() and bcache_read_bytes()
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=6a57ed17a28aee0e8fb...
Commit: 6a57ed17a28aee0e8fb9557ec3c03a02f0b2a4be
Parent: 467adfa082c3be10d012fa156db7810d23221648
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Mon Feb 5 16:56:56 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] add bcache_prefetch_bytes() and bcache_read_bytes()
Not tested yet.
---
lib/device/bcache.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++
lib/device/bcache.h | 10 ++++++++
2 files changed, 69 insertions(+), 0 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index 86b56c0..1d83306 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -966,5 +966,64 @@ void bcache_invalidate_fd(struct bcache *cache, int fd)
_recycle_block(cache, b);
}
+static void byte_range_to_block_range(struct bcache *cache, off_t start, size_t len,
+ block_address *bb, block_address *be)
+{
+ block_address block_size = cache->block_sectors << SECTOR_SHIFT;
+ *bb = start / block_size;
+ *be = (start + len + block_size - 1) / block_size;
+}
+
+void bcache_prefetch_bytes(struct bcache *cache, int fd, off_t start, size_t len)
+{
+ block_address bb, be;
+
+ byte_range_to_block_range(cache, start, len, &bb, &be);
+ while (bb < be) {
+ bcache_prefetch(cache, fd, bb);
+ bb++;
+ }
+}
+
+static off_t _min(off_t lhs, off_t rhs)
+{
+ if (rhs > lhs)
+ return rhs;
+
+ return lhs;
+}
+
+bool bcache_read_bytes(struct bcache *cache, int fd, off_t start, size_t len, void *data)
+{
+ struct block *b;
+ block_address bb, be, i;
+ unsigned char *udata = data;
+ off_t block_size = cache->block_sectors << SECTOR_SHIFT;
+
+ byte_range_to_block_range(cache, start, len, &bb, &be);
+ for (i = bb; i < be; i++)
+ bcache_prefetch(cache, fd, i);
+
+ for (i = bb; i < be; i++) {
+ if (!bcache_get(cache, fd, i, 0, &b))
+ return false;
+
+ if (i == bb) {
+ off_t block_offset = start % block_size;
+ size_t blen = _min(block_size - block_offset, len);
+ memcpy(udata, ((unsigned char *) b->data) + block_offset, blen);
+ len -= blen;
+ udata += blen;
+ } else {
+ size_t blen = _min(block_size, len);
+ memcpy(udata, b->data, blen);
+ len -= blen;
+ udata += blen;
+ }
+ }
+
+ return true;
+}
+
//----------------------------------------------------------------
diff --git a/lib/device/bcache.h b/lib/device/bcache.h
index 818dee2..7d38d33 100644
--- a/lib/device/bcache.h
+++ b/lib/device/bcache.h
@@ -137,6 +137,16 @@ void bcache_invalidate(struct bcache *cache, int fd, block_address index);
*/
void bcache_invalidate_fd(struct bcache *cache, int fd);
+/*
+ * Prefetches the blocks neccessary to satisfy a byte range.
+ */
+void bcache_prefetch_bytes(struct bcache *cache, int fd, off_t start, size_t len);
+
+/*
+ * Reads the bytes.
+ */
+bool bcache_read_bytes(struct bcache *cache, int fd, off_t start, size_t len, void *data);
+
/*----------------------------------------------------------------*/
#endif
6 years, 1 month
master - [device/bcache] More tests and some bug fixes
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=467adfa082c3be10d01...
Commit: 467adfa082c3be10d012fa156db7810d23221648
Parent: 8ae3b244fcbc207b51a81514e51008fe64d13368
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Mon Feb 5 16:04:23 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] More tests and some bug fixes
---
lib/device/bcache.c | 127 ++++++-----
lib/device/bcache.h | 32 +++-
test/unit/Makefile.in | 26 ++-
test/unit/bcache_t.c | 589 +++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 616 insertions(+), 158 deletions(-)
diff --git a/lib/device/bcache.c b/lib/device/bcache.c
index e5d0e1b..86b56c0 100644
--- a/lib/device/bcache.c
+++ b/lib/device/bcache.c
@@ -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);
}
diff --git a/lib/device/bcache.h b/lib/device/bcache.h
index 14204be..818dee2 100644
--- a/lib/device/bcache.h
+++ b/lib/device/bcache.h
@@ -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 {
diff --git a/test/unit/Makefile.in b/test/unit/Makefile.in
index 2e2c819..a070329 100644
--- a/test/unit/Makefile.in
+++ b/test/unit/Makefile.in
@@ -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
diff --git a/test/unit/bcache_t.c b/test/unit/bcache_t.c
index 3db9cc7..c2d2df0 100644
--- a/test/unit/bcache_t.c
+++ b/test/unit/bcache_t.c
@@ -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 MEG 2048
-#define SECTOR_SHIFT 9
+#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 const char *_test_path = "test.bin";
-int bcache_init(void)
+static void _fail(const char *fmt, ...)
{
- return 0;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ longjmp(_test_k, TEST_FAILED);
}
-int bcache_fini(void)
+#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)
{
- return 0;
+ 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 int open_file(const char *path)
+static void _expect_write(struct mock_engine *e)
{
- return open(path, O_EXCL | O_RDWR | O_DIRECT, 0666);
+ // FIXME: finish
+ _expect(e, E_ISSUE);
}
-static int _prep_file(const char *path)
+static void _match(struct mock_engine *e, enum method m)
{
- int fd, r;
+ struct mock_call *mc;
+
+ if (dm_list_empty(&e->expected_calls))
+ _fail("unexpected call to method %s\n", _show_method(m));
- fd = open(path, O_CREAT | O_TRUNC | O_EXCL | O_RDWR | O_DIRECT, 0666);
- if (fd < 0)
- return -1;
+ mc = dm_list_item(e->expected_calls.n, struct mock_call);
+ dm_list_del(&mc->list);
- r = fallocate(fd, FALLOC_FL_ZERO_RANGE, 0, (1 * MEG) << SECTOR_SHIFT);
- if (r) {
- close(fd);
- return -1;
+ 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));
+}
- close(fd);
- return 0;
+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 int test_init(void)
+static bool _mock_issue(struct io_engine *e, enum dir d, int fd,
+ sector_t sb, sector_t se, void *data, void *context)
{
- unlink(_test_path);
- return _prep_file(_test_path);
+ 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 int test_exit(void)
+static bool _mock_wait(struct io_engine *e, io_complete_fn fn)
{
- unlink(_test_path);
- return 0;
+ 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 void test_create(void)
+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 void good_create(sector_t block_size, unsigned nr_cache_blocks)
{
- struct bcache *cache = bcache_create(8, 16);
- CU_ASSERT_PTR_NOT_NULL(cache);
+ 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);
}
+static void bad_create(sector_t block_size, unsigned nr_cache_blocks)
+{
+ 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);
+ me->e.destroy(&me->e);
+}
+
+static void test_create(void)
+{
+ 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};
+ int fd = 17; // arbitrary key
+ struct block *b;
- for (i = 0; i < DM_ARRAY_SIZE(_bad_examples); i++) {
- cache = bcache_create(_bad_examples[i], 16);
- CU_ASSERT_PTR_NULL(cache);
- }
+ _expect(me, E_ISSUE);
+ _expect(me, E_WAIT);
+ T_ASSERT(bcache_get(cache, fd, 0, 0, &b));
+ bcache_put(b);
}
+ _expect(me, E_DESTROY);
+ bcache_destroy(cache);
+}
+
+static void test_repeated_reads_are_cached(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);
+
{
- // 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);
+ int fd = 17; // arbitrary key
+ unsigned i;
+ struct block *b;
+
+ _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);
}
}
+
+ _expect(me, E_DESTROY);
+ bcache_destroy(cache);
}
-static void test_reads_work(void)
+static void test_block_gets_evicted_with_many_reads(void)
{
- int fd;
+ const unsigned nr_cache_blocks = 16;
+ 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, nr_cache_blocks, &me->e);
+ T_ASSERT(cache);
{
- int i;
+ int fd = 17; // arbitrary key
+ unsigned i;
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);
+ 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);
+ }
+
+ // 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);
- bcache_destroy(cache);
+ // 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_works(void)
+static void test_prefetch_issues_a_read(void)
{
- int fd;
+ const unsigned nr_cache_blocks = 16;
+ 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, nr_cache_blocks, &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++)
+ 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 < 16; i++) {
- CU_ASSERT(bcache_get(cache, fd, i, 0, &b));
+ for (i = 0; i < nr_cache_blocks; i++) {
+ _expect(me, E_WAIT);
+ T_ASSERT(bcache_get(cache, fd, i, 0, &b));
bcache_put(b);
}
+ }
- bcache_destroy(cache);
+ _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);
}
- close(fd);
+ _expect(me, E_DESTROY);
+ bcache_destroy(cache);
+}
- test_exit();
+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;
+}
6 years, 1 month
master - [build] include test/unit/Makefile rather than recursive build
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=8ae3b244fcbc207b51a...
Commit: 8ae3b244fcbc207b51a81514e51008fe64d13368
Parent: b03e55a5130ffdf6be9188b227c59e6793dc0dfc
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Fri Feb 2 15:39:17 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[build] include test/unit/Makefile rather than recursive build
FIXME: unit tests are not currently run as part of make check.
---
Makefile.in | 23 +------------------
test/Makefile.in | 2 +-
test/unit/Makefile.in | 58 ++++++++++++++++---------------------------------
3 files changed, 21 insertions(+), 62 deletions(-)
diff --git a/Makefile.in b/Makefile.in
index 31d428d..146ed55 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -212,28 +212,7 @@ endif
endif
ifeq ("$(TESTING)", "yes")
-# testing and report generation
-RUBY=ruby1.9 -Ireport-generators/lib -Ireport-generators/test
-
-.PHONY: unit-test ruby-test test-programs
-
-# FIXME: put dependencies on libdm and liblvm
-# FIXME: Should be handled by Makefiles in subdirs, not here at top level.
-test-programs:
- cd unit-tests/regex && $(MAKE)
- cd unit-tests/datastruct && $(MAKE)
- cd unit-tests/mm && $(MAKE)
-
-unit-test: test-programs
- $(RUBY) report-generators/unit_test.rb $(shell find . -name TESTS)
- $(RUBY) report-generators/title_page.rb
-
-memcheck: test-programs
- $(RUBY) report-generators/memcheck.rb $(shell find . -name TESTS)
- $(RUBY) report-generators/title_page.rb
-
-ruby-test:
- $(RUBY) report-generators/test/ts.rb
+include test/unit/Makefile
endif
ifneq ($(shell which ctags),)
diff --git a/test/Makefile.in b/test/Makefile.in
index 230ce5b..097b2fa 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -27,7 +27,7 @@ datarootdir = @datarootdir@
LVM_TEST_RESULTS ?= results
-SUBDIRS = api unit
+SUBDIRS = api
SOURCES = lib/not.c lib/harness.c
CXXSOURCES = lib/runner.cpp
CXXFLAGS += $(EXTRA_EXEC_CFLAGS)
diff --git a/test/unit/Makefile.in b/test/unit/Makefile.in
index 5cf92ba..2e2c819 100644
--- a/test/unit/Makefile.in
+++ b/test/unit/Makefile.in
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2017 Red Hat, Inc. All rights reserved.
+# Copyright (C) 2011-2018 Red Hat, Inc. All rights reserved.
#
# This file is part of LVM2.
#
@@ -10,44 +10,24 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-srcdir = @srcdir@
-top_srcdir = @top_srcdir@
-top_builddir = @top_builddir@
-
-VPATH = $(srcdir)
-UNITS = \
- bcache_t.c \
- bitset_t.c\
- config_t.c\
- dmlist_t.c\
- dmstatus_t.c\
- matcher_t.c\
- percent_t.c\
- string_t.c\
- run.c
-
-ifeq ("@TESTING@", "yes")
-SOURCES = $(UNITS)
-TARGETS = run
-endif
-
-include $(top_builddir)/make.tmpl
-
-ifeq ($(MAKECMDGOALS),distclean)
-SOURCES = $(UNITS)
-endif
-
-ifeq ("$(TESTING)", "yes")
-LDLIBS += $(LVMINTERNAL_LIBS) -ldevmapper -laio @CUNIT_LIBS@
-CFLAGS += @CUNIT_CFLAGS@
-
-check: unit
-
-$(TARGETS): $(OBJECTS) $(top_builddir)/libdm/libdevmapper.$(LIB_SUFFIX)
+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
+UNIT_OBJECTS=$(UNIT_SOURCE:%.c=%.o)
+
+UNIT_LDLIBS += $(LVMINTERNAL_LIBS) -ldevmapper -laio -lcunit
+
+test/unit/run: $(UNIT_OBJECTS) libdm/libdevmapper.$(LIB_SUFFIX) lib/liblvm-internal.a
$(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) -L$(top_builddir)/libdm \
- -o $@ $(OBJECTS) $(LDLIBS)
+ -o $@ $(UNIT_OBJECTS) $(UNIT_LDLIBS)
-unit: $(TARGETS) $(top_builddir)/lib/liblvm-internal.a
+unit-test: test/unit/run
@echo Running unit tests
- LD_LIBRARY_PATH=$(top_builddir)/libdm ./$(TARGETS)
-endif
+ LD_LIBRARY_PATH=libdm test/unit/run
6 years, 1 month
master - [device/bcache] rename a unit test
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=b03e55a5130ffdf6be9...
Commit: b03e55a5130ffdf6be9188b227c59e6793dc0dfc
Parent: 0d0fab3d2ddb0c0f16c01e569e3f1f218701592e
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Fri Feb 2 15:38:46 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] rename a unit test
---
test/unit/bcache_t.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/unit/bcache_t.c b/test/unit/bcache_t.c
index 5532c92..3db9cc7 100644
--- a/test/unit/bcache_t.c
+++ b/test/unit/bcache_t.c
@@ -175,7 +175,7 @@ static void test_prefetch_works(void)
}
#define NR_FILES 4
-static void test_multiple_files(void)
+static void test_read_multiple_files(void)
{
unsigned i;
int fd[NR_FILES];
@@ -226,6 +226,6 @@ CU_TestInfo bcache_list[] = {
{ (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*)"multiple files", test_multiple_files },
+ { (char*)"read multiple files", test_read_multiple_files },
CU_TEST_INFO_NULL
};
6 years, 1 month
master - [device/bcache] another unit test
by David Teigland
Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=0d0fab3d2ddb0c0f16c...
Commit: 0d0fab3d2ddb0c0f16c01e569e3f1f218701592e
Parent: 19647d1cd44d029a8aa2f7e74dbdbd1f114a8c08
Author: Joe Thornber <ejt(a)redhat.com>
AuthorDate: Fri Feb 2 14:35:11 2018 +0000
Committer: David Teigland <teigland(a)redhat.com>
CommitterDate: Fri Apr 20 11:12:50 2018 -0500
[device/bcache] another unit test
---
test/unit/bcache_t.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 47 insertions(+), 1 deletions(-)
diff --git a/test/unit/bcache_t.c b/test/unit/bcache_t.c
index ef92721..5532c92 100644
--- a/test/unit/bcache_t.c
+++ b/test/unit/bcache_t.c
@@ -50,7 +50,7 @@ static int _prep_file(const char *path)
if (fd < 0)
return -1;
- r = fallocate(fd, FALLOC_FL_ZERO_RANGE, 0, (16 * MEG) << SECTOR_SHIFT);
+ r = fallocate(fd, FALLOC_FL_ZERO_RANGE, 0, (1 * MEG) << SECTOR_SHIFT);
if (r) {
close(fd);
return -1;
@@ -174,6 +174,51 @@ static void test_prefetch_works(void)
test_exit();
}
+#define NR_FILES 4
+static void test_multiple_files(void)
+{
+ unsigned i;
+ int fd[NR_FILES];
+ char buffer[128];
+
+
+ // FIXME: add fixtures.
+ test_init();
+ for (i = 0; i < NR_FILES; i++) {
+ snprintf(buffer, sizeof(buffer), "./test%u.bin", i);
+ unlink(buffer);
+ _prep_file(buffer);
+ fd[i] = open_file(buffer);
+ CU_ASSERT(fd[i] >= 0);
+ }
+
+ {
+ struct block *b;
+ struct bcache *cache = bcache_create(8, 16);
+
+ for (i = 0; i < 64; i++) {
+ if (!bcache_get(cache, fd[i % NR_FILES], i, 0, &b)) {
+ CU_ASSERT(false);
+ } else
+ bcache_put(b);
+ }
+
+ bcache_destroy(cache);
+ }
+
+ for (i = 0; i < NR_FILES; i++)
+ close(fd[i]);
+
+ test_exit();
+}
+
+// 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 },
@@ -181,5 +226,6 @@ CU_TestInfo bcache_list[] = {
{ (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*)"multiple files", test_multiple_files },
CU_TEST_INFO_NULL
};
6 years, 1 month