diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..fcd0034 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + project: + default: + target: 96% + threshold: 2% diff --git a/libs/market/book.hpp b/libs/market/book.hpp index 89c160a..5818474 100644 --- a/libs/market/book.hpp +++ b/libs/market/book.hpp @@ -5,11 +5,12 @@ #pragma once +#include "common/utils.hpp" #include "market.hpp" -#include #include #include +#include #include #include @@ -189,19 +190,17 @@ namespace market { for (; i < size_i; ++i) { freel[i] = i; } - tail_i = --i; + tail_i = size_i - 1; side_i[0] = side_i[1] = 0; - ASSERT(side_i[0] + side_i[1] + (size_type)(tail_i + 1) == size_i); }; void accept() { ASSERT(freel != nullptr); - size_type i = 0; - for (; i < size_i; ++i) { + for (size_type i = 0; i < size_i; ++i) { freel[i] = i; } // Mark elements in the free list, which are not actually free, with npos - for (i = 0; i < side_i[0] || i < side_i[1]; ++i) { + for (size_type i = 0; i < side_i[0] || i < side_i[1]; ++i) { if (i < side_i[0]) { freel[sides[i]] = npos; } @@ -209,10 +208,12 @@ namespace market { freel[sides[capacity + i]] = npos; } } + // Note: tail_i will underflow to npos if no space left, by design + tail_i = size_i - (size_type)1 - side_i[0] - side_i[1]; // Shift freel elements marked as taken (i.e. with npos value) to the end of freel + // Note: npos will not be preserved, it has no special meaning outside of accept auto* const begin = &freel[0]; std::remove_if(begin, begin + size_i, [](size_type n){ return n == npos; } ); - ASSERT(side_i[0] + side_i[1] + (size_type)(tail_i + 1) == size_i); }; public: @@ -271,7 +272,6 @@ namespace market { template void sort() { - ASSERT(freel != nullptr); auto* const begin = &sides[(size_t)Side * capacity]; std::sort(begin, begin + side_i[(size_t)Side], [this](size_type lh, size_type rh){ return book::compare(levels[lh], levels[rh]); @@ -297,7 +297,6 @@ namespace market { template level& at(size_type i) { - ASSERT(freel != nullptr); ASSERT(i < side_i[(size_t)Side]); const auto l = sides[(size_t)Side * capacity + i]; return levels[l]; diff --git a/tests/book.cpp b/tests/book.cpp index 34d47b2..611fcee 100644 --- a/tests/book.cpp +++ b/tests/book.cpp @@ -3,7 +3,10 @@ // Distributed under the MIT License. See accompanying file LICENSE // or copy at https://opensource.org/licenses/MIT -#include +struct assert_error {}; +#define ASSERT(...) do { if((__VA_ARGS__) == 0) throw assert_error{}; } while(0) + +#include "market/book.hpp" #include @@ -427,6 +430,25 @@ namespace { : book(levels, sides, freel, i, 0, 0, std::forward(t)...) { this->accept(); } + + template + size_type populate(Level const (&input)[Size], size_type offset) { + size_type i = 0; + for (auto const &l : input) { + sides[(size_t)Side * capacity + i] = offset + i; + levels[offset + i++] = l; // Note: must post-increment + } + side_i[(size_t)Side] = i; + return i; + } + + using market::book::reset; + using market::book::accept; + + size_type *&base_freel() { return market::book::freel; } + size_type &base_tail() { return market::book::tail_i; } + size_type &base_size_bid() { return market::book::side_i[0]; } + size_type &base_size_ask() { return market::book::side_i[1]; } }; } @@ -549,6 +571,81 @@ TEST_CASE("AnySizeBook_construction", "[book][capacity][construction][bad_capaci REQUIRE(ptr->emplace_back() == npos); REQUIRE(ptr->emplace_back() == npos); } + + SECTION("call accept() on populated book") { + Level bids[2] = {Level{130130, 100}, Level{130140, 10}}; + Level offers[3] = {Level{130190, 50}, Level{130180, 40}, Level{130170, 20}}; + + CHECK_NOTHROW(ptr.reset(new AnySizeBook(10, std::nothrow))); + REQUIRE(ptr->capacity == 10); + auto const offset = ptr->populate(bids, 0); + ptr->accept(); + for (std::size_t i = 0; i < 20; ++i) { + CHECK((i >= 18 ? true : ptr->freel[i] == i + 2)); + } + CHECK(ptr->size() == 2); + CHECK(ptr->size() == 0); + ptr->populate(offers, offset); + ptr->accept(); + for (std::size_t i = 0; i < 20; ++i) { + CHECK((i >= 15 ? true : ptr->freel[i] == i + 5)); + } + CHECK(ptr->size() == 2); + CHECK(ptr->size() == 3); + } + + SECTION("ASSERT check") { + CHECK_NOTHROW(ptr.reset(new AnySizeBook(10, std::nothrow))); + SECTION("null freel") { + ptr->base_freel() = nullptr; + CHECK_THROWS_AS(ptr->reset(), assert_error); + CHECK_THROWS_AS(ptr->accept(), assert_error); + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + } + SECTION("bad bid size") { + ptr->base_size_bid() = 1; + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + } + + SECTION("bad ask size") { + ptr->base_size_ask() = 1; + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + } + + SECTION("bad tail") { + ptr->base_tail() = 1; + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->push_back(Level{1, 1}), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->emplace_back(1, 1), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + } + + SECTION("bad index") { + CHECK_THROWS_AS(std::as_const(*ptr).at(0), assert_error); + CHECK_THROWS_AS(std::as_const(*ptr).at(0), assert_error); + CHECK_THROWS_AS(ptr->at(0), assert_error); + CHECK_THROWS_AS(ptr->at(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + CHECK_THROWS_AS(ptr->remove(0), assert_error); + } + } } TEST_CASE("AnySizeBook_binary_search", "[book][binary_search]") { diff --git a/tests/market.cpp b/tests/market.cpp index 8f2efd8..3a614f1 100644 --- a/tests/market.cpp +++ b/tests/market.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT License. See accompanying file LICENSE // or copy at https://opensource.org/licenses/MIT -#include +#include "market/market.hpp" #include diff --git a/tests/utils.cpp b/tests/utils.cpp index 2da3fe9..f936961 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT License. See accompanying file LICENSE // or copy at https://opensource.org/licenses/MIT -#include +#include "common/utils.hpp" #include