diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 03e205da..3042f023 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2019-2019 tsurugi project. +# Copyright 2019-2025 tsurugi project. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -132,9 +132,20 @@ else () "datastore/two_recovery/*.cpp" "datastore/three_recovery/*.cpp" ) + file (GLOB TEST_WITH_LIMESTONE_DOUBLE_SOURCES + "test_double/*.cpp" + ) endif() endif() +file (GLOB LIMESTONE_DOUBLE_SOURCES "test_double/doubles/limestone_double.cpp") +add_library(limestone-double ${LIMESTONE_DOUBLE_SOURCES}) +target_link_libraries(limestone-double PRIVATE limestone) +target_include_directories(limestone-double + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + register_tests( TARGET shirakami DEPENDS @@ -179,5 +190,19 @@ else () SOURCES ${LOGGING_TEST_SOURCES} TEST_LABELS "LOGGING" ) + register_tests( + TARGET shirakami + DEPENDS + shirakami-impl + PRIVATE glog::glog + PRIVATE ${tbb_prefix}tbb + PRIVATE ${tbb_prefix}tbbmalloc + PRIVATE ${tbb_prefix}tbbmalloc_proxy + PRIVATE yakushima + PRIVATE atomic + PRIVATE limestone-double + SOURCES ${TEST_WITH_LIMESTONE_DOUBLE_SOURCES} + TEST_LABELS "LOGGING" + ) endif() endif () diff --git a/test/test_double/blob_mock_test.cpp b/test/test_double/blob_mock_test.cpp new file mode 100644 index 00000000..8e594537 --- /dev/null +++ b/test/test_double/blob_mock_test.cpp @@ -0,0 +1,85 @@ + +#include "concurrency_control/include/session.h" + +#include "gtest/gtest.h" +#include "glog/logging.h" + +#include "test_tool.h" +#include "doubles/limestone_double.h" + +namespace shirakami::testing { + +using namespace shirakami; + +class blob_mock_test : public ::testing::Test { +public: + static void call_once_f() { + google::InitGoogleLogging("shirakami-test-test_double-blob_mock_test"); + // FLAGS_stderrthreshold = 0; // output more than INFO + } + void SetUp() override { + std::call_once(init_google_, call_once_f); + init(); // NOLINT + } + + void TearDown() override { + fin(); + test_double::log_channel_add_entry1::hook_func = nullptr; + test_double::log_channel_add_entry2::hook_func = nullptr; + test_double::datastore_switch_available_boundary_version::hook_func = nullptr; + } + +private: + static inline std::once_flag init_google_; +}; + +TEST_F(blob_mock_test, insert_update_blob) { + std::vector>> added; + // prepare + + // test double + test_double::log_channel_add_entry1::hook_func = [&added] ( + [[maybe_unused]] test_double::log_channel_add_entry1::orig_type orig_func, + [[maybe_unused]] limestone::api::log_channel* this_ptr, + [[maybe_unused]] limestone::api::storage_id_type storage_id, + [[maybe_unused]] std::string_view key, [[maybe_unused]] std::string_view value, + [[maybe_unused]] limestone::api::write_version_type write_version) -> void { + VLOG(40) << "add_entry 1 storage_id:" << storage_id << " key:" << key; + added.emplace_back(key, std::vector{}); + }; + test_double::log_channel_add_entry2::hook_func = [&added] ( + [[maybe_unused]] test_double::log_channel_add_entry2::orig_type orig_func, + [[maybe_unused]] limestone::api::log_channel* this_ptr, + [[maybe_unused]] limestone::api::storage_id_type storage_id, + [[maybe_unused]] std::string_view key, [[maybe_unused]] std::string_view value, + [[maybe_unused]] limestone::api::write_version_type write_version, + [[maybe_unused]] const std::vector& large_objects) -> void { + VLOG(40) << "add_entry 2 storage_id:" << storage_id << " key:" << key << shirakami_vecstring(large_objects); + added.emplace_back(key, large_objects); + }; + + Storage st{}; + create_storage("", st); // N.B. create_storage may call add_entry + Token s{}; + ASSERT_OK(enter(s)); + added.clear(); + + // test and verify + ASSERT_EQ(added.size(), 0); + ASSERT_OK(tx_begin({s, transaction_options::transaction_type::SHORT})); + const blob_id_type b1[2] = {11, 22}; + ASSERT_OK(insert(s, st, "k", "v", b1, 2)); + const blob_id_type b2[3] = {99, 88, 77}; + ASSERT_OK(update(s, st, "k", "v1", b2, 3)); + ASSERT_OK(commit(s)); + { + ASSERT_EQ(added.size(), 1); + auto lobs = std::get<1>(added.at(0)); + EXPECT_EQ(std::vector(std::begin(b2), std::end(b2)), lobs); + } + + // cleanup + ASSERT_OK(leave(s)); +} + +} // namespace shirakami::testing diff --git a/test/test_double/doubles/limestone_double.cpp b/test/test_double/doubles/limestone_double.cpp new file mode 100644 index 00000000..05708d57 --- /dev/null +++ b/test/test_double/doubles/limestone_double.cpp @@ -0,0 +1,111 @@ +#define _GNU_SOURCE 1 +#include + +#include "glog/logging.h" + +#include "limestone_double.h" + +#define EXTERN_C 1 + +namespace test_double::log_channel_add_entry1 { hook_type hook_func = nullptr; } +namespace test_double::log_channel_add_entry2 { hook_type hook_func = nullptr; } +namespace test_double::datastore_switch_available_boundary_version { hook_type hook_func = nullptr; } + +#if EXTERN_C == 0 + +namespace limestone::api { + +void log_channel::add_entry(storage_id_type storage_id, std::string_view key, std::string_view value, write_version_type write_version) { + using namespace test_double::log_channel_add_entry1; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this, storage_id, key, value, write_version); + } else { + return orig_ptr.load()(this, storage_id, key, value, write_version); + } +} + +void log_channel::add_entry(storage_id_type storage_id, std::string_view key, std::string_view value, write_version_type write_version, const std::vector& large_objects) { + using namespace test_double::log_channel_add_entry2; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this, storage_id, key, value, write_version, large_objects); + } else { + return orig_ptr.load()(this, storage_id, key, value, write_version, large_objects); + } +} + +void datastore::switch_available_boundary_version(write_version_type version) { + using namespace test_double::datastore_switch_available_boundary_version; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this, version); + } else { + return orig_ptr.load()(this, version); + } +} + +} + +#else + +extern "C" { + +void _ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeE(limestone::api::log_channel* this_ptr, limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version) { + using namespace test_double::log_channel_add_entry1; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this_ptr, storage_id, key, value, write_version); + } else { + return orig_ptr.load()(this_ptr, storage_id, key, value, write_version); + } +} + +void _ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeERKSt6vectorImSaImEE(limestone::api::log_channel* this_ptr, limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version, const std::vector& large_objects) { + using namespace test_double::log_channel_add_entry2; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this_ptr, storage_id, key, value, write_version, large_objects); + } else { + return orig_ptr.load()(this_ptr, storage_id, key, value, write_version, large_objects); + } +} + +void _ZN9limestone3api9datastore33switch_available_boundary_versionENS0_18write_version_typeE(limestone::api::datastore* this_ptr, limestone::api::write_version_type version) { + using namespace test_double::datastore_switch_available_boundary_version; + static std::atomic orig_ptr{nullptr}; + if (!orig_ptr) { + orig_ptr = reinterpret_cast(dlsym(RTLD_NEXT, sym_name)); + } + LOG_IF(FATAL, !orig_ptr) << "dlsym " << sym_name << " failed"; + if (hook_func) { + return hook_func(orig_ptr.load(), this_ptr, version); + } else { + return orig_ptr.load()(this_ptr, version); + } +} + +} +#endif + +#undef EXTERN_C diff --git a/test/test_double/doubles/limestone_double.h b/test/test_double/doubles/limestone_double.h new file mode 100644 index 00000000..22c6fcb0 --- /dev/null +++ b/test/test_double/doubles/limestone_double.h @@ -0,0 +1,41 @@ +#pragma once + +#include "limestone/api/write_version_type.h" +#include "limestone/api/datastore.h" + +namespace test_double { + +// MEMBER_FUNC_DEF +// nickname log_channel_add_entry1 +// cppname void limestone::api::log_channel::add_entry(limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version) +// cname _ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeE +namespace log_channel_add_entry1 { + using orig_type = void(*)(limestone::api::log_channel*, limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version); + using hook_type = std::function; + extern hook_type hook_func; + constexpr const char sym_name[] = "_ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeE"; +} + +// MEMBER_FUNC_DEF +// nickname log_channel_add_entry2 +// cppname void limestone::api::log_channel::add_entry(limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version, const std::vector& large_objects) +// cname _ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeERKSt6vectorImSaImEE +namespace log_channel_add_entry2 { + using orig_type = void(*)(limestone::api::log_channel*, limestone::api::storage_id_type storage_id, std::string_view key, std::string_view value, limestone::api::write_version_type write_version, const std::vector& large_objects); + using hook_type = std::function& large_objects)>; + extern hook_type hook_func; + constexpr const char sym_name[] = "_ZN9limestone3api11log_channel9add_entryEmSt17basic_string_viewIcSt11char_traitsIcEES5_NS0_18write_version_typeERKSt6vectorImSaImEE"; +} + +// MEMBER_FUNC_DEF +// nickname datastore_switch_available_boundary_version +// cppname void limestone::api::datastore::switch_available_boundary_version(limestone::api::write_version_type version) +// cname _ZN9limestone3api9datastore33switch_available_boundary_versionENS0_18write_version_typeE +namespace datastore_switch_available_boundary_version { + using orig_type = void(*)(limestone::api::datastore*, limestone::api::write_version_type write_version); + using hook_type = std::function; + extern hook_type hook_func; + constexpr const char sym_name[] = "_ZN9limestone3api9datastore33switch_available_boundary_versionENS0_18write_version_typeE"; +} + +}