diff --git a/Makefile.in b/Makefile.in index d2e1e97..1fa391b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/block-cache/copier.cc b/block-cache/copier.cc index 192c0a1..9910b58 100644 --- a/block-cache/copier.cc +++ b/block-cache/copier.cc @@ -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_, - io_engine::READ, - to_sector(op.src_b), - to_sector(op.src_e), - data, - key); - jobs_.insert(make_pair(key, job)); + 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 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_, - 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); + 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)) { + complete(j); + jobs_.erase(it); + } } else { j.op.write_complete = true; - jobs_.erase(it); complete(j); + jobs_.erase(it); } } diff --git a/block-cache/copier.h b/block-cache/copier.h index 24e1b38..5eb2a51 100644 --- a/block-cache/copier.h +++ b/block-cache/copier.h @@ -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_; diff --git a/block-cache/io_engine.cc b/block-cache/io_engine.cc index 5580c8a..7837c33 100644 --- a/block-cache/io_engine.cc +++ b/block-cache/io_engine.cc @@ -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,71 +107,60 @@ 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(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(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 -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(r); i++) { - io_event const &e = events_[i]; - iocb *cb = reinterpret_cast(e.obj); - unsigned context = cbs_.context(cb); + iocb *cb = reinterpret_cast(event.obj); + unsigned context = cbs_.context(cb); + + if (event.res == cb->u.c.nbytes) { cbs_.free(cb); + return make_pair(true, context); - if (e.res == cb->u.c.nbytes) - return make_pair(true, context); + } else if (static_cast(event.res) < 0) { + cbs_.free(cb); + return make_pair(false, 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; - return make_pair(false, context); - } + } else { + cbs_.free(cb); + return make_pair(false, context); } - // shouldn't get here return make_pair(false, 0); } diff --git a/block-cache/io_engine.h b/block-cache/io_engine.h index 07d97a8..55b556d 100644 --- a/block-cache/io_engine.h +++ b/block-cache/io_engine.h @@ -5,16 +5,50 @@ #include #include -#include #include #include +#include //---------------------------------------------------------------- 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; + 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 wait(); + virtual wait_result wait(); private: std::list descriptors_; io_context_t aio_context_; control_block_set cbs_; - std::vector 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; }; } diff --git a/caching/cache_writeback.cc b/caching/cache_writeback.cc index d81655a..f7b9a28 100644 --- a/caching/cache_writeback.cc +++ b/caching/cache_writeback.cc @@ -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; diff --git a/unit-tests/Makefile.in b/unit-tests/Makefile.in index 8746456..cee3a70 100644 --- a/unit-tests/Makefile.in +++ b/unit-tests/Makefile.in @@ -16,10 +16,10 @@ # with thin-provisioning-tools. If not, see # . -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 \ diff --git a/unit-tests/copier_t.cc b/unit-tests/copier_t.cc index b366ff8..e95819f 100644 --- a/unit-tests/copier_t.cc +++ b/unit-tests/copier_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 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(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 engine_; }; } //---------------------------------------------------------------- TEST_F(CopierTests, empty_test) +{ + auto c = make_copier(); +} + +TEST_F(CopierTests, successful_copy) +{ + // Copy first block + copy_op op1(0, 1, 0); + + 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); - 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(); + + 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); } } diff --git a/unit-tests/io_engine_t.cc b/unit-tests/io_engine_t.cc new file mode 100644 index 0000000..f1b3a9e --- /dev/null +++ b/unit-tests/io_engine_t.cc @@ -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 +// . + +#include "gmock/gmock.h" +#include "block-cache/mem_pool.h" +#include "block-cache/io_engine.h" +#include "test_utils.h" + + +#include + +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 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); + +} + +//---------------------------------------------------------------- diff --git a/unit-tests/mem_pool_t.cc b/unit-tests/mem_pool_t.cc index 24fb34c..43e05ec 100644 --- a/unit-tests/mem_pool_t.cc +++ b/unit-tests/mem_pool_t.cc @@ -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); +} + //---------------------------------------------------------------- diff --git a/unit-tests/test_utils.cc b/unit-tests/test_utils.cc index 5a9cc96..85876bd 100644 --- a/unit-tests/test_utils.cc +++ b/unit-tests/test_utils.cc @@ -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"); +} + +//---------------------------------------------------------------- diff --git a/unit-tests/test_utils.h b/unit-tests/test_utils.h index bdd63e5..e979d07 100644 --- a/unit-tests/test_utils.h +++ b/unit-tests/test_utils.h @@ -114,6 +114,18 @@ namespace test { std::unique_ptr 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