From 0c7e7bce18558ea04e120b5fddcf4d22c0abc785 Mon Sep 17 00:00:00 2001 From: Lee Clagett Date: Wed, 26 Sep 2018 23:54:27 -0400 Subject: Adding classes, functions, and utilities for common LMDB operations. --- tests/unit_tests/CMakeLists.txt | 2 + tests/unit_tests/lmdb.cpp | 404 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 406 insertions(+) create mode 100644 tests/unit_tests/lmdb.cpp (limited to 'tests/unit_tests') diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index b355d566d..a819f76a4 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -56,6 +56,7 @@ set(unit_tests_sources keccak.cpp logging.cpp long_term_block_weight.cpp + lmdb.cpp main.cpp memwipe.cpp mlocker.cpp @@ -101,6 +102,7 @@ target_link_libraries(unit_tests cryptonote_protocol cryptonote_core blockchain_db + lmdb_lib rpc net serialization diff --git a/tests/unit_tests/lmdb.cpp b/tests/unit_tests/lmdb.cpp new file mode 100644 index 000000000..c37c83a32 --- /dev/null +++ b/tests/unit_tests/lmdb.cpp @@ -0,0 +1,404 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +#include "lmdb/database.h" +#include "lmdb/table.h" +#include "lmdb/transaction.h" +#include "lmdb/util.h" + +namespace +{ + enum class choice : unsigned {}; + enum class big_choice : unsigned long {}; + + struct bytes { + char data[16]; + }; + + MONERO_CURSOR(test_cursor); + + template + int run_compare(T left, T right, MDB_cmp_func* cmp) + { + MDB_val left_val = lmdb::to_val(left); + MDB_val right_val = lmdb::to_val(right); + return (*cmp)(&left_val, &right_val); + } +} + +TEST(LMDB, Traits) +{ + EXPECT_TRUE((std::is_same::type>())); + EXPECT_TRUE((std::is_same::type>())); + + EXPECT_TRUE((std::is_same>())); + EXPECT_TRUE((std::is_same>())); + EXPECT_TRUE((std::is_same>())); + EXPECT_TRUE((std::is_same>())); +} + +TEST(LMDB, ToNative) +{ + enum class negative_choice : int {}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE( + (std::is_same()) + ); + EXPECT_TRUE( + (std::is_same()) + ); + + EXPECT_EQ(unsigned(0), lmdb::to_native(choice(0))); + EXPECT_EQ(unsigned(0xffffffff), lmdb::to_native(choice(0xffffffff))); + EXPECT_EQ(-1, lmdb::to_native(negative_choice(-1))); + + // test constexpr + static_assert(100 == lmdb::to_native(choice(100)), "to_native failed"); + static_assert(-100 == lmdb::to_native(negative_choice(-100)), "to_native failed"); +} + +TEST(LMDB, Conversions) +{ + struct one + { + big_choice i; + choice j; + }; + + const one test{big_choice(100), choice(95)}; + one test2{big_choice(1000), choice(950)}; + + EXPECT_EQ(&test, lmdb::to_val(test).mv_data); + EXPECT_NE(&test2, lmdb::to_val(test).mv_data); + EXPECT_EQ( + &test, + static_cast(lmdb::to_byte_span(lmdb::to_val(test)).begin()) + ); + EXPECT_EQ(sizeof(test), lmdb::to_val(test).mv_size); + EXPECT_EQ(sizeof(test), lmdb::to_byte_span(lmdb::to_val(test)).size()); + + EXPECT_EQ(&test2, lmdb::to_val(test2).mv_data); + EXPECT_NE(&test, lmdb::to_val(test2).mv_data); + EXPECT_EQ( + &test2, + static_cast(lmdb::to_byte_span(lmdb::to_val(test2)).begin()) + ); + EXPECT_EQ(sizeof(test2), lmdb::to_val(test2).mv_size); + EXPECT_EQ(sizeof(test2), lmdb::to_byte_span(lmdb::to_val(test2)).size()); +} + +TEST(LMDB, LessSort) +{ + struct one + { + unsigned i; + unsigned j; + }; + + struct two + { + unsigned i; + choice j; + }; + + EXPECT_EQ(0, run_compare(0u, 0u, &lmdb::less)); + EXPECT_EQ(-1, run_compare(0u, 1u, &lmdb::less)); + EXPECT_EQ(1, run_compare(1u, 0u, &lmdb::less)); + + EXPECT_EQ(0, run_compare({0, 1}, {0, 1}, &lmdb::less)); + EXPECT_EQ(-1, run_compare({0, 0}, {0, 1}, &lmdb::less)); + EXPECT_EQ(1, run_compare({0, 1}, {0, 0}, &lmdb::less)); + + EXPECT_EQ(0, run_compare({0, 1}, {0, 1}, MONERO_SORT_BY(one, j))); + EXPECT_EQ(-1, run_compare({0, 0}, {0, 1}, MONERO_SORT_BY(one, j))); + EXPECT_EQ(1, run_compare({0, 1}, {0, 0}, MONERO_SORT_BY(one, j))); + + EXPECT_EQ(0, run_compare({0, choice(1)}, {0, choice(1)}, MONERO_SORT_BY(two, j))); + EXPECT_EQ(-1, run_compare({0, choice(0)}, {0, choice(1)}, MONERO_SORT_BY(two, j))); + EXPECT_EQ(1, run_compare({0, choice(1)}, {0, choice(0)}, MONERO_SORT_BY(two, j))); + + // compare function addresses + EXPECT_EQ((MONERO_SORT_BY(one, i)), (MONERO_SORT_BY(two, i))); + EXPECT_EQ((MONERO_SORT_BY(one, j)), (MONERO_SORT_BY(two, j))); + EXPECT_NE((MONERO_SORT_BY(one, i)), (MONERO_SORT_BY(two, j))); + EXPECT_NE((MONERO_SORT_BY(one, j)), (MONERO_SORT_BY(two, i))); +} + +TEST(LMDB, SortCompare) +{ + struct one + { + unsigned i; + bytes j; + }; + + one test{55}; + boost::iota(test.j.data, 10); + + const one test2 = test; + + EXPECT_EQ(0, run_compare(test, test2, MONERO_COMPARE(one, j))); + + test.j.data[15] = 1; + EXPECT_GT(0, run_compare(test, test2, MONERO_COMPARE(one, j))); + + test.j.data[15] = 100; + EXPECT_LT(0, run_compare(test, test2, MONERO_COMPARE(one, j))); +} + +TEST(LMDB, Table) +{ + struct one + { + bytes i; + bytes j; + }; + + constexpr lmdb::basic_table test{"foo"}; + + EXPECT_STREQ("foo", test.name); + static_assert(test.flags == 0, "bad flags"); + static_assert(&lmdb::less == test.key_cmp, "bad key_cmp"); + static_assert(test.value_cmp == nullptr, "bad value_cmp"); + EXPECT_TRUE(test.get_value(MDB_val{}).matches(std::errc::invalid_argument)); + + lmdb::basic_table test2{ + "foo2", MDB_DUPSORT, &lmdb::compare + }; + + EXPECT_STREQ("foo2", test2.name); + EXPECT_EQ((MDB_DUPSORT | MDB_DUPFIXED), test2.flags); + EXPECT_EQ(&lmdb::less, test2.key_cmp); + EXPECT_EQ(&lmdb::compare, test2.value_cmp); + EXPECT_TRUE(test2.get_value(MDB_val{}).matches(std::errc::invalid_argument)); + + one record{}; + boost::iota(record.i.data, 0); + boost::iota(record.i.data, 20); + + const one record_copy = MONERO_UNWRAP(test2.get_value(lmdb::to_val(record))); + EXPECT_TRUE(boost::equal(record.i.data, record_copy.i.data)); + EXPECT_TRUE(boost::equal(record.j.data, record_copy.j.data)); + + const bytes j_copy = MONERO_UNWRAP( + test2.get_value(lmdb::to_val(record)) + ); + EXPECT_TRUE(boost::equal(record.j.data, j_copy.data)); + + EXPECT_TRUE( + test.get_key_stream(test_cursor{}).matches(std::errc::invalid_argument) + ); + EXPECT_TRUE( + test2.get_key_stream(test_cursor{}).matches(std::errc::invalid_argument) + ); + + + EXPECT_TRUE( + test.get_value_stream(choice(0), test_cursor{}).matches(std::errc::invalid_argument) + ); + EXPECT_TRUE( + test2.get_value_stream(big_choice(0), test_cursor{}).matches(std::errc::invalid_argument) + ); +} + +TEST(LMDB, InvalidDatabase) +{ + lmdb::database test{lmdb::environment{}}; + + EXPECT_TRUE(test.resize().matches(std::errc::invalid_argument)); + EXPECT_TRUE(test.create_read_txn().matches(std::errc::invalid_argument)); + EXPECT_TRUE(test.reset_txn(lmdb::read_txn{}).matches(std::errc::invalid_argument)); + EXPECT_TRUE(test.create_write_txn().matches(std::errc::invalid_argument)); + EXPECT_TRUE(test.commit(lmdb::write_txn{}).matches(std::errc::invalid_argument)); + + EXPECT_TRUE( + test.try_write( [](MDB_txn&) { return success(); } ).matches(std::errc::invalid_argument) + ); +} + +TEST(LMDB, InvalidValueStream) +{ + struct one + { + choice i; + choice j; + bytes k; + }; + + lmdb::value_stream test{test_cursor{}}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE( + (std::is_same()))>()) + ); + EXPECT_TRUE( + (std::is_same().begin()))>()) + ); + + EXPECT_NO_THROW(test.reset()); + EXPECT_EQ(0u, test.count()); + EXPECT_TRUE(test.make_iterator().is_end()); + EXPECT_TRUE(test.make_range().empty()); + EXPECT_EQ(nullptr, test.give_cursor()); + + EXPECT_EQ(0u, test.count()); + EXPECT_TRUE(test.make_iterator().is_end()); + EXPECT_TRUE(test.make_range().empty()); + EXPECT_EQ(nullptr, test.give_cursor()); +} + +TEST(LMDB, InvalidValueIterator) +{ + struct one + { + choice i; + choice j; + bytes k; + }; + + lmdb::value_iterator test1{}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE( + (std::is_same())>()) + ); + + EXPECT_TRUE(test1.is_end()); + EXPECT_NO_THROW(++test1); + EXPECT_NO_THROW(test1++); + EXPECT_TRUE(test1.is_end()); + + lmdb::value_iterator test2{nullptr}; + + EXPECT_TRUE(test2.is_end()); + EXPECT_NO_THROW(++test2); + EXPECT_NO_THROW(test2++); + EXPECT_TRUE(test2.is_end()); + + EXPECT_TRUE(test1.equal(test2)); + EXPECT_TRUE(test2.equal(test1)); + EXPECT_TRUE(test1 == test2); + EXPECT_TRUE(test2 == test1); + EXPECT_FALSE(test1 != test2); + EXPECT_FALSE(test2 != test1); + + lmdb::value_iterator test3{}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())>())); + EXPECT_TRUE( + (std::is_same())>()) + ); + + EXPECT_TRUE(test3.is_end()); + EXPECT_NO_THROW(++test3); + EXPECT_NO_THROW(test3++); + EXPECT_TRUE(test3.is_end()); +} + +TEST(LMDB, InvalidKeyStream) +{ + struct one + { + choice i; + choice j; + bytes k; + }; + + using record = std::pair>>; + + lmdb::key_stream test{test_cursor{}}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())); + + EXPECT_NO_THROW(test.reset()); + EXPECT_TRUE(test.make_iterator().is_end()); + EXPECT_TRUE(test.make_range().empty()); + EXPECT_EQ(nullptr, test.give_cursor()); + + EXPECT_TRUE(test.make_iterator().is_end()); + EXPECT_TRUE(test.make_range().empty()); + EXPECT_EQ(nullptr, test.give_cursor()); +} + +TEST(LMDB, InvalidKeyIterator) +{ + struct one + { + choice i; + choice j; + bytes k; + }; + + using record = std::pair>>; + + lmdb::key_iterator test1{}; + + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE((std::is_same())); + EXPECT_TRUE( + (std::is_same()))>()) + ); + EXPECT_TRUE( + (std::is_same().begin()))>()) + ); + + EXPECT_TRUE(test1.is_end()); + EXPECT_NO_THROW(++test1); + EXPECT_NO_THROW(test1++); + EXPECT_TRUE(test1.is_end()); + EXPECT_TRUE(test1.make_value_iterator().is_end()); + EXPECT_TRUE(test1.make_value_range().empty()); + + lmdb::key_iterator test2{nullptr}; + + EXPECT_TRUE(test2.is_end()); + EXPECT_NO_THROW(++test2); + EXPECT_NO_THROW(test2++); + EXPECT_TRUE(test2.is_end()); + EXPECT_TRUE(test2.make_value_iterator().is_end()); + EXPECT_TRUE(test2.make_value_range().empty()); + + EXPECT_TRUE(test1.equal(test2)); + EXPECT_TRUE(test2.equal(test1)); + EXPECT_TRUE(test1 == test2); + EXPECT_TRUE(test2 == test1); + EXPECT_FALSE(test1 != test2); + EXPECT_FALSE(test2 != test1); +} + + -- cgit v1.2.3