[block-cache] unit tests + debug io_engine and copier
This commit is contained in:
parent
34c039d7dc
commit
a94bfea798
@ -166,8 +166,8 @@ INSTALL_DATA = $(INSTALL) -p -m 644
|
||||
|
||||
ifeq ("@TESTING@", "yes")
|
||||
TEST_INCLUDES=\
|
||||
-Igmock-1.6.0/include \
|
||||
-Igmock-1.6.0/gtest/include
|
||||
-Igoogletest/googlemock/include \
|
||||
-Igoogletest/googletest/include
|
||||
else
|
||||
TEST_INCLUDES=
|
||||
endif
|
||||
|
@ -8,18 +8,25 @@ using namespace std;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
copier::copier(string const &src, string const &dest,
|
||||
copier::copier(io_engine &engine,
|
||||
string const &src, string const &dest,
|
||||
sector_t block_size, size_t mem)
|
||||
: pool_(block_size, mem),
|
||||
: pool_(block_size * 512, mem),
|
||||
block_size_(block_size),
|
||||
nr_blocks_(mem / block_size),
|
||||
engine_(nr_blocks_),
|
||||
engine_(engine),
|
||||
src_handle_(engine_.open_file(src, io_engine::READ_ONLY)),
|
||||
dest_handle_(engine_.open_file(dest, io_engine::READ_WRITE)),
|
||||
genkey_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
copier::~copier()
|
||||
{
|
||||
engine_.close_file(src_handle_);
|
||||
engine_.close_file(dest_handle_);
|
||||
}
|
||||
|
||||
void
|
||||
copier::issue(copy_op const &op)
|
||||
{
|
||||
@ -37,14 +44,17 @@ copier::issue(copy_op const &op)
|
||||
job.op.read_complete = job.op.write_complete = false;
|
||||
unsigned key = genkey(); // used as context for the io_engine
|
||||
|
||||
cerr << "data = " << data << "\n";
|
||||
engine_.issue_io(src_handle_,
|
||||
auto r = engine_.issue_io(src_handle_,
|
||||
io_engine::READ,
|
||||
to_sector(op.src_b),
|
||||
to_sector(op.src_e),
|
||||
data,
|
||||
key);
|
||||
|
||||
if (r)
|
||||
jobs_.insert(make_pair(key, job));
|
||||
else
|
||||
complete(job);
|
||||
}
|
||||
|
||||
unsigned
|
||||
@ -56,7 +66,7 @@ copier::nr_pending() const
|
||||
boost::optional<copy_op>
|
||||
copier::wait()
|
||||
{
|
||||
while (complete_.empty() && !jobs_.empty())
|
||||
while (!jobs_.empty() && complete_.empty())
|
||||
wait_();
|
||||
|
||||
if (complete_.empty())
|
||||
@ -77,28 +87,31 @@ copier::wait_()
|
||||
if (it == jobs_.end())
|
||||
throw runtime_error("Internal error. Lost track of copy job.");
|
||||
|
||||
copy_job j = it->second;
|
||||
copy_job &j = it->second;
|
||||
if (!p.first) {
|
||||
// IO was unsuccessful
|
||||
jobs_.erase(it);
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
// IO was successful
|
||||
if (!j.op.read_complete) {
|
||||
j.op.read_complete = true;
|
||||
engine_.issue_io(dest_handle_,
|
||||
if (!engine_.issue_io(dest_handle_,
|
||||
io_engine::WRITE,
|
||||
to_sector(j.op.dest_b),
|
||||
to_sector(j.op.dest_b + (j.op.src_e - j.op.src_b)),
|
||||
j.data,
|
||||
it->first);
|
||||
it->first)) {
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
}
|
||||
|
||||
} else {
|
||||
j.op.write_complete = true;
|
||||
jobs_.erase(it);
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,13 @@ namespace bcache {
|
||||
|
||||
struct copy_op {
|
||||
copy_op()
|
||||
: read_complete(false),
|
||||
: src_b(0),
|
||||
src_e(0),
|
||||
dest_b(0),
|
||||
read_complete(false),
|
||||
write_complete(false) {
|
||||
}
|
||||
|
||||
|
||||
copy_op(block_address src_b_,
|
||||
block_address src_e_,
|
||||
block_address dest_b_)
|
||||
@ -30,6 +32,10 @@ namespace bcache {
|
||||
write_complete(false) {
|
||||
}
|
||||
|
||||
bool success() const {
|
||||
return read_complete && write_complete;
|
||||
}
|
||||
|
||||
block_address src_b, src_e;
|
||||
block_address dest_b;
|
||||
|
||||
@ -49,8 +55,10 @@ namespace bcache {
|
||||
|
||||
class copier {
|
||||
public:
|
||||
copier(std::string const &src, std::string const &dest,
|
||||
copier(io_engine &engine,
|
||||
std::string const &src, std::string const &dest,
|
||||
sector_t block_size, size_t mem);
|
||||
~copier();
|
||||
|
||||
sector_t get_block_size() const {
|
||||
return block_size_;
|
||||
@ -72,7 +80,7 @@ namespace bcache {
|
||||
mempool pool_;
|
||||
sector_t block_size_;
|
||||
unsigned nr_blocks_;
|
||||
io_engine engine_;
|
||||
io_engine &engine_;
|
||||
io_engine::handle src_handle_;
|
||||
io_engine::handle dest_handle_;
|
||||
unsigned genkey_count_;
|
||||
|
@ -12,7 +12,12 @@ using namespace bcache;
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
|
||||
#define SECTOR_SHIFT 9
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
unsigned const SECTOR_SHIFT = 9;
|
||||
unsigned const PAGE_SIZE = 4096;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
@ -55,23 +60,22 @@ control_block_set::context(iocb *cb) const
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
io_engine::io_engine(unsigned max_io)
|
||||
aio_engine::aio_engine(unsigned max_io)
|
||||
: aio_context_(0),
|
||||
cbs_(max_io),
|
||||
events_(max_io)
|
||||
cbs_(max_io)
|
||||
{
|
||||
int r = io_setup(max_io, &aio_context_);
|
||||
if (r < 0)
|
||||
throw runtime_error("io_setup failed");
|
||||
}
|
||||
|
||||
io_engine::~io_engine()
|
||||
aio_engine::~aio_engine()
|
||||
{
|
||||
io_destroy(aio_context_);
|
||||
}
|
||||
|
||||
io_engine::handle
|
||||
io_engine:: open_file(std::string const &path, mode m)
|
||||
aio_engine::handle
|
||||
aio_engine::open_file(std::string const &path, mode m)
|
||||
{
|
||||
int flags = (m == READ_ONLY) ? O_RDONLY : O_RDWR;
|
||||
int fd = ::open(path.c_str(), O_DIRECT | flags);
|
||||
@ -87,7 +91,7 @@ io_engine:: open_file(std::string const &path, mode m)
|
||||
}
|
||||
|
||||
void
|
||||
io_engine::close_file(handle h)
|
||||
aio_engine::close_file(handle h)
|
||||
{
|
||||
for (auto it = descriptors_.begin(); it != descriptors_.end(); ++it) {
|
||||
unsigned it_h = it->get();
|
||||
@ -103,70 +107,59 @@ io_engine::close_file(handle h)
|
||||
}
|
||||
|
||||
bool
|
||||
io_engine::issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context)
|
||||
aio_engine::issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context)
|
||||
{
|
||||
auto cb = cbs_.alloc(context);
|
||||
if (reinterpret_cast<uint64_t>(data) & (PAGE_SIZE - 1))
|
||||
throw runtime_error("Data passed to issue_io must be page aligned\n");
|
||||
|
||||
iocb *cb;
|
||||
|
||||
cb = cbs_.alloc(context);
|
||||
if (!cb)
|
||||
return false;
|
||||
|
||||
memset(cb, 0, sizeof(*cb));
|
||||
|
||||
cb->aio_fildes = static_cast<int>(h);
|
||||
cb->u.c.buf = data;
|
||||
cb->u.c.offset = b << SECTOR_SHIFT;
|
||||
cb->u.c.nbytes = (e - b) << SECTOR_SHIFT;
|
||||
|
||||
cb->aio_lio_opcode = (d == READ) ? IO_CMD_PREAD : IO_CMD_PWRITE;
|
||||
|
||||
int r = io_submit(aio_context_, 1, &cb);
|
||||
if (r != 1) {
|
||||
std::ostringstream out;
|
||||
out << "couldn't issue "
|
||||
<< ((d == READ) ? "READ" : "WRITE")
|
||||
<< " io: io_submit ";
|
||||
if (r < 0)
|
||||
out << "failed with " << r;
|
||||
else
|
||||
out << "succeeded, but queued no io";
|
||||
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
|
||||
return true;
|
||||
return r == 1;
|
||||
}
|
||||
|
||||
std::pair<bool, io_engine::handle>
|
||||
io_engine::wait()
|
||||
aio_engine::wait()
|
||||
{
|
||||
int r;
|
||||
unsigned i;
|
||||
struct io_event event;
|
||||
|
||||
r = io_getevents(aio_context_, 1, events_.size(), &events_[0], NULL);
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
r = io_getevents(aio_context_, 1, 1, &event, NULL);
|
||||
if (r < 0) {
|
||||
std::ostringstream out;
|
||||
out << "io_getevents failed: " << r;
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
|
||||
for (i = 0; i < static_cast<unsigned>(r); i++) {
|
||||
io_event const &e = events_[i];
|
||||
iocb *cb = reinterpret_cast<iocb *>(e.obj);
|
||||
iocb *cb = reinterpret_cast<iocb *>(event.obj);
|
||||
unsigned context = cbs_.context(cb);
|
||||
cbs_.free(cb);
|
||||
|
||||
if (e.res == cb->u.c.nbytes)
|
||||
if (event.res == cb->u.c.nbytes) {
|
||||
cbs_.free(cb);
|
||||
return make_pair(true, context);
|
||||
|
||||
else {
|
||||
std::ostringstream out;
|
||||
out << "io failed"
|
||||
<< ", e.res = " << e.res
|
||||
<< ", e.res2 = " << e.res2
|
||||
<< ", offset = " << cb->u.c.offset
|
||||
<< ", nbytes = " << cb->u.c.nbytes;
|
||||
} else if (static_cast<int>(event.res) < 0) {
|
||||
cbs_.free(cb);
|
||||
return make_pair(false, context);
|
||||
|
||||
} else {
|
||||
cbs_.free(cb);
|
||||
return make_pair(false, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// shouldn't get here
|
||||
return make_pair(false, 0);
|
||||
|
@ -5,16 +5,50 @@
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <ctype.h>
|
||||
#include <libaio.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <libaio.h>
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace bcache {
|
||||
using sector_t = uint64_t;
|
||||
|
||||
//----------------
|
||||
// Virtual base class to aid unit testing
|
||||
class io_engine {
|
||||
public:
|
||||
enum mode {
|
||||
READ_ONLY,
|
||||
READ_WRITE
|
||||
};
|
||||
|
||||
enum dir {
|
||||
READ,
|
||||
WRITE
|
||||
};
|
||||
|
||||
io_engine() {}
|
||||
virtual ~io_engine() {}
|
||||
|
||||
using handle = unsigned;
|
||||
|
||||
virtual handle open_file(std::string const &path, mode m) = 0;
|
||||
virtual void close_file(handle h) = 0;
|
||||
|
||||
// returns false if there are insufficient resources to
|
||||
// queue the IO
|
||||
virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context) = 0;
|
||||
|
||||
// returns (success, context)
|
||||
using wait_result = std::pair<bool, unsigned>;
|
||||
virtual wait_result wait() = 0;
|
||||
|
||||
private:
|
||||
io_engine(io_engine const &) = delete;
|
||||
io_engine &operator =(io_engine const &) = delete;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
class control_block_set {
|
||||
public:
|
||||
@ -37,43 +71,32 @@ namespace bcache {
|
||||
|
||||
//----------------
|
||||
|
||||
class io_engine {
|
||||
class aio_engine : public io_engine {
|
||||
public:
|
||||
enum mode {
|
||||
READ_ONLY,
|
||||
READ_WRITE
|
||||
};
|
||||
|
||||
enum dir {
|
||||
READ,
|
||||
WRITE
|
||||
};
|
||||
|
||||
// max_io is the maximum nr of concurrent ios expected
|
||||
io_engine(unsigned max_io);
|
||||
~io_engine();
|
||||
aio_engine(unsigned max_io);
|
||||
~aio_engine();
|
||||
|
||||
using handle = unsigned;
|
||||
|
||||
handle open_file(std::string const &path, mode m);
|
||||
void close_file(handle h);
|
||||
// FIXME: open exclusive?
|
||||
virtual handle open_file(std::string const &path, mode m);
|
||||
virtual void close_file(handle h);
|
||||
|
||||
// returns false if there are insufficient resources to
|
||||
// queue the IO
|
||||
bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context);
|
||||
// Returns false if queueing the io failed
|
||||
virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context);
|
||||
|
||||
// returns (success, context)
|
||||
std::pair<bool, unsigned> wait();
|
||||
virtual wait_result wait();
|
||||
|
||||
private:
|
||||
std::list<base::unique_fd> descriptors_;
|
||||
|
||||
io_context_t aio_context_;
|
||||
control_block_set cbs_;
|
||||
std::vector<io_event> events_;
|
||||
|
||||
io_engine(io_engine const &) = delete;
|
||||
io_engine &operator =(io_engine const &) = delete;
|
||||
aio_engine(io_engine const &) = delete;
|
||||
aio_engine &operator =(io_engine const &) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -120,8 +120,8 @@ namespace {
|
||||
int writeback_(flags const &f) {
|
||||
block_manager<>::ptr bm = open_bm(*f.metadata_dev, block_manager<>::READ_ONLY);
|
||||
metadata md(bm, metadata::OPEN);
|
||||
|
||||
copier c(*f.fast_dev, *f.origin_dev,
|
||||
aio_engine engine(f.cache_size / md.sb_.data_block_size);
|
||||
copier c(engine, *f.fast_dev, *f.origin_dev,
|
||||
md.sb_.data_block_size, f.cache_size);
|
||||
copy_visitor cv(c, clean_shutdown(md));
|
||||
ignore_damage_visitor dv;
|
||||
|
@ -16,10 +16,10 @@
|
||||
# with thin-provisioning-tools. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
GMOCK_DIR=gmock-1.6.0/
|
||||
GMOCK_DIR=googletest
|
||||
GMOCK_INCLUDES=\
|
||||
-Igmock-1.6.0/include \
|
||||
-Igmock-1.6.0/gtest/include
|
||||
-I$(GMOCK_DIR)/googlemock/include \
|
||||
-I$(GMOCK_DIR)/googletest/include
|
||||
|
||||
GMOCK_FLAGS=\
|
||||
-Wno-unused-local-typedefs
|
||||
@ -28,16 +28,16 @@ GMOCK_LIBS=\
|
||||
-Llib -lpdata -lgmock -lpthread -laio
|
||||
|
||||
GMOCK_DEPS=\
|
||||
$(wildcard $(GMOCK_DIR)/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/src/*.cc) \
|
||||
$(wildcard $(GMOCK_DIR)/gtest/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/gtest/src/*.cc)
|
||||
$(wildcard $(GMOCK_DIR)/googlemock/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/googlemock/src/*.cc) \
|
||||
$(wildcard $(GMOCK_DIR)/googletest/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/googletest/src/*.cc)
|
||||
|
||||
lib/libgmock.a: $(GMOCK_DEPS)
|
||||
@echo " [CXX] gtest"
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/gtest -c $(GMOCK_DIR)/gtest/src/gtest-all.cc
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googletest -c $(GMOCK_DIR)/googletest/src/gtest-all.cc
|
||||
@echo " [CXX] gmock"
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR) -c $(GMOCK_DIR)/src/gmock-all.cc
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googlemock -c $(GMOCK_DIR)/googlemock/src/gmock-all.cc
|
||||
@echo " [AR] $<"
|
||||
$(V)ar -rv lib/libgmock.a gtest-all.o gmock-all.o > /dev/null 2>&1
|
||||
|
||||
@ -59,6 +59,7 @@ TEST_SOURCE=\
|
||||
unit-tests/damage_tracker_t.cc \
|
||||
unit-tests/endian_t.cc \
|
||||
unit-tests/error_state_t.cc \
|
||||
unit-tests/io_engine_t.cc \
|
||||
unit-tests/mem_pool_t.cc \
|
||||
unit-tests/rmap_visitor_t.cc \
|
||||
unit-tests/rolling_hash_t.cc \
|
||||
|
@ -31,72 +31,197 @@ using namespace testing;
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
class temp_file {
|
||||
class io_engine_mock : public io_engine {
|
||||
public:
|
||||
temp_file(string const &name_base, unsigned meg_size)
|
||||
: path_(gen_path(name_base)) {
|
||||
MOCK_METHOD2(open_file, handle(string const &, mode));
|
||||
MOCK_METHOD1(close_file, void(handle));
|
||||
MOCK_METHOD6(issue_io, bool(handle, dir, sector_t, sector_t, void *, unsigned));
|
||||
|
||||
int fd = ::open(path_.c_str(), O_CREAT | O_RDWR, 0666);
|
||||
if (fd < 0)
|
||||
throw runtime_error("couldn't open file");
|
||||
|
||||
if (::fallocate(fd, 0, 0, 1024 * 1024 * meg_size))
|
||||
throw runtime_error("couldn't fallocate");
|
||||
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
~temp_file() {
|
||||
::unlink(path_.c_str());
|
||||
}
|
||||
|
||||
string const &get_path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
private:
|
||||
static string gen_path(string const &base) {
|
||||
return string("./") + base + string(".tmp");
|
||||
}
|
||||
|
||||
string path_;
|
||||
MOCK_METHOD0(wait, wait_result());
|
||||
};
|
||||
|
||||
unsigned const BLOCK_SIZE = 64u;
|
||||
using wait_result = io_engine::wait_result;
|
||||
|
||||
class CopierTests : public Test {
|
||||
public:
|
||||
CopierTests()
|
||||
: src_file_("copy_src", 32),
|
||||
dest_file_("copy_dest", 32),
|
||||
copier_(src_file_.get_path(),
|
||||
dest_file_.get_path(),
|
||||
64, 1 * 1024 * 1024) {
|
||||
: src_file_("copy_src"),
|
||||
dest_file_("copy_dest") {
|
||||
}
|
||||
|
||||
copier &get_copier() {
|
||||
return copier_;
|
||||
unique_ptr<copier> make_copier() {
|
||||
EXPECT_CALL(engine_, open_file(src_file_, io_engine::READ_ONLY)).
|
||||
WillOnce(Return(SRC_HANDLE));
|
||||
EXPECT_CALL(engine_, open_file(dest_file_, io_engine::READ_WRITE)).
|
||||
WillOnce(Return(DEST_HANDLE));
|
||||
|
||||
EXPECT_CALL(engine_, close_file(SRC_HANDLE)).Times(1);
|
||||
EXPECT_CALL(engine_, close_file(DEST_HANDLE)).Times(1);
|
||||
|
||||
return unique_ptr<copier>(new copier(engine_, src_file_,
|
||||
dest_file_,
|
||||
BLOCK_SIZE, 1 * 1024 * 1024));
|
||||
}
|
||||
|
||||
private:
|
||||
temp_file src_file_;
|
||||
temp_file dest_file_;
|
||||
void issue_successful_op(copier &c, copy_op &op, unsigned context) {
|
||||
InSequence dummy;
|
||||
|
||||
copier copier_;
|
||||
unsigned nr_pending = c.nr_pending();
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::READ,
|
||||
op.src_b * BLOCK_SIZE,
|
||||
op.src_e * BLOCK_SIZE, _, context)).
|
||||
WillOnce(Return(true));
|
||||
c.issue(op);
|
||||
|
||||
ASSERT_TRUE(c.nr_pending() == nr_pending + 1);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(true, context)));
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::WRITE,
|
||||
op.dest_b * BLOCK_SIZE,
|
||||
(op.dest_b + (op.src_e - op.src_b)) * BLOCK_SIZE, _, context)).
|
||||
WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(true, context)));
|
||||
|
||||
auto mop = c.wait();
|
||||
ASSERT_EQ(c.nr_pending(), nr_pending);
|
||||
|
||||
ASSERT_TRUE(mop->success());
|
||||
}
|
||||
|
||||
unsigned const SRC_HANDLE = 10;
|
||||
unsigned const DEST_HANDLE = 11;
|
||||
|
||||
string src_file_;
|
||||
string dest_file_;
|
||||
StrictMock<io_engine_mock> engine_;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
TEST_F(CopierTests, empty_test)
|
||||
{
|
||||
auto c = make_copier();
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, successful_copy)
|
||||
{
|
||||
// Copy first block
|
||||
copy_op op1(0, 1, 0);
|
||||
get_copier().issue(op1);
|
||||
auto mop = get_copier().wait();
|
||||
|
||||
if (mop) {
|
||||
cerr << "op completed\n";
|
||||
} else {
|
||||
cerr << "no op completed\n";
|
||||
auto c = make_copier();
|
||||
issue_successful_op(*c, op1, 0);
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_issue_read)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(false));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_read)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(false, 0u)));
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_issue_write)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(true, 0u)));
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::WRITE, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(false));
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_write)
|
||||
{
|
||||
// Copy first block
|
||||
copy_op op1(0, 1, 0);
|
||||
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(true, 0u)));
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::WRITE, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(wait_result(false, 0u)));
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, copy_the_same_block_many_times)
|
||||
{
|
||||
auto c = make_copier();
|
||||
copy_op op1(0, 1, 0);
|
||||
|
||||
for (unsigned i = 0; i < 50000; i++)
|
||||
issue_successful_op(*c, op1, i);
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, copy_different_blocks)
|
||||
{
|
||||
auto c = make_copier();
|
||||
for (unsigned i = 0; i < 5000; i++) {
|
||||
copy_op op(i, i + 1, i);
|
||||
issue_successful_op(*c, op, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
185
unit-tests/io_engine_t.cc
Normal file
185
unit-tests/io_engine_t.cc
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright (C) 2016 Red Hat, Inc. All rights reserved.
|
||||
//
|
||||
// This file is part of the thin-provisioning-tools source.
|
||||
//
|
||||
// thin-provisioning-tools is free software: you can redistribute it
|
||||
// and/or modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
//
|
||||
// thin-provisioning-tools is distributed in the hope that it will be
|
||||
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with thin-provisioning-tools. If not, see
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "block-cache/mem_pool.h"
|
||||
#include "block-cache/io_engine.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
using namespace testing;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
unsigned const MAX_IO = 64;
|
||||
unsigned const PAGE_SIZE = 4096;
|
||||
|
||||
class IOEngineTests : public Test {
|
||||
public:
|
||||
IOEngineTests()
|
||||
: pool_(64 * 512, 128 * 512, PAGE_SIZE),
|
||||
src_file_("copy_src", 32),
|
||||
dest_file_("copy_dest", 32),
|
||||
engine_(new aio_engine(MAX_IO)) {
|
||||
}
|
||||
|
||||
// in sectors
|
||||
static uint64_t meg(unsigned n) {
|
||||
return 2 * 1024 * n;
|
||||
}
|
||||
|
||||
mempool pool_;
|
||||
temp_file src_file_;
|
||||
temp_file dest_file_;
|
||||
unique_ptr<io_engine> engine_;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
TEST_F(IOEngineTests, empty_test)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, open_and_close)
|
||||
{
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
auto dest_handle = engine_->open_file(dest_file_.get_path(), io_engine::READ_WRITE);
|
||||
ASSERT_TRUE(src_handle != dest_handle);
|
||||
engine_->close_file(src_handle);
|
||||
engine_->close_file(dest_handle);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, you_can_read_a_read_only_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::READ,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr.first);
|
||||
ASSERT_TRUE(wr.second == 123);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(IOEngineTests, you_cannot_write_to_a_read_only_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::WRITE,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
0);
|
||||
ASSERT_FALSE(r);
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, you_can_write_to_a_read_write_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::READ,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr.first);
|
||||
ASSERT_TRUE(wr.second == 123);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, final_block_read_succeeds)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::READ,
|
||||
meg(32) - nr_sectors, meg(32),
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr.first);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, out_of_bounds_read_fails)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::READ,
|
||||
meg(32), meg(32) + nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_FALSE(wr.first);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, out_of_bounds_write_succeeds)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto handle = engine_->open_file(dest_file_.get_path(), io_engine::READ_WRITE);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(handle,
|
||||
io_engine::WRITE,
|
||||
meg(32), meg(32) + nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr.first);
|
||||
|
||||
engine_->close_file(handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
@ -93,4 +93,20 @@ TEST_F(MempoolTests, exhaust_pool)
|
||||
ASSERT_EQ(md, nullptr);
|
||||
}
|
||||
|
||||
// Use valgrind
|
||||
TEST_F(MempoolTests, data_can_be_written)
|
||||
{
|
||||
mempool mp(512, 100 * 512, 512);
|
||||
|
||||
for (unsigned i = 0; i < 100; i++) {
|
||||
auto md = mp.alloc();
|
||||
ASSERT_NE(md, nullptr);
|
||||
|
||||
memset(md, 0, 512);
|
||||
}
|
||||
|
||||
auto md = mp.alloc();
|
||||
ASSERT_EQ(md, nullptr);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "persistent-data/space-maps/core.h"
|
||||
|
||||
using namespace persistent_data;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
@ -21,3 +23,35 @@ test::open_temporary_tm(block_manager<>::ptr bm)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
temp_file::temp_file(string const &name_base, unsigned meg_size)
|
||||
: path_(gen_path(name_base))
|
||||
{
|
||||
int fd = ::open(path_.c_str(), O_CREAT | O_RDWR, 0666);
|
||||
if (fd < 0)
|
||||
throw runtime_error("couldn't open file");
|
||||
|
||||
if (::fallocate(fd, 0, 0, 1024 * 1024 * meg_size))
|
||||
throw runtime_error("couldn't fallocate");
|
||||
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
temp_file::~temp_file()
|
||||
{
|
||||
// ::unlink(path_.c_str());
|
||||
}
|
||||
|
||||
string const &
|
||||
temp_file::get_path() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
string
|
||||
temp_file::gen_path(string const &base)
|
||||
{
|
||||
return string("./") + base + string(".tmp");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
@ -114,6 +114,18 @@ namespace test {
|
||||
std::unique_ptr<with_directory> dir_;
|
||||
};
|
||||
|
||||
class temp_file {
|
||||
public:
|
||||
temp_file(std::string const &name_base, unsigned meg_size);
|
||||
~temp_file();
|
||||
std::string const &get_path() const;
|
||||
|
||||
private:
|
||||
static string gen_path(string const &base);
|
||||
|
||||
string path_;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
template <typename T>
|
||||
|
Loading…
Reference in New Issue
Block a user