// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). // // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "rocksdb/customizable.h" #include #include #include #include #include #include "db/db_test_util.h" #include "memory/jemalloc_nodump_allocator.h" #include "memory/memkind_kmem_allocator.h" #include "options/options_helper.h" #include "options/options_parser.h" #include "port/stack_trace.h" #include "rocksdb/convenience.h" #include "rocksdb/env_encryption.h" #include "rocksdb/file_checksum.h" #include "rocksdb/filter_policy.h" #include "rocksdb/flush_block_policy.h" #include "rocksdb/memory_allocator.h" #include "rocksdb/secondary_cache.h" #include "rocksdb/slice_transform.h" #include "rocksdb/sst_partitioner.h" #include "rocksdb/statistics.h" #include "rocksdb/utilities/customizable_util.h" #include "rocksdb/utilities/object_registry.h" #include "rocksdb/utilities/options_type.h" #include "table/block_based/filter_policy_internal.h" #include "table/block_based/flush_block_policy.h" #include "table/mock_table.h" #include "test_util/mock_time_env.h" #include "test_util/testharness.h" #include "test_util/testutil.h" #include "util/file_checksum_helper.h" #include "util/string_util.h" #include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h" #include "utilities/memory_allocators.h" #include "utilities/merge_operators/bytesxor.h" #include "utilities/merge_operators/sortlist.h" #include "utilities/merge_operators/string_append/stringappend.h" #include "utilities/merge_operators/string_append/stringappend2.h" #ifndef GFLAGS bool FLAGS_enable_print = false; #else #include "util/gflags_compat.h" using GFLAGS_NAMESPACE::ParseCommandLineFlags; DEFINE_bool(enable_print, false, "Print options generated to console."); #endif // GFLAGS namespace ROCKSDB_NAMESPACE { namespace { class StringLogger : public Logger { public: using Logger::Logv; void Logv(const char* format, va_list ap) override { char buffer[1000]; vsnprintf(buffer, sizeof(buffer), format, ap); string_.append(buffer); } const std::string& str() const { return string_; } void clear() { string_.clear(); } private: std::string string_; }; class TestCustomizable : public Customizable { public: TestCustomizable(const std::string& name) : name_(name) {} // Method to allow CheckedCast to work for this class static const char* kClassName() { return "TestCustomizable"; } const char* Name() const override { return name_.c_str(); } static const char* Type() { return "test.custom"; } #ifndef ROCKSDB_LITE static Status CreateFromString(const ConfigOptions& opts, const std::string& value, std::unique_ptr* result); static Status CreateFromString(const ConfigOptions& opts, const std::string& value, std::shared_ptr* result); static Status CreateFromString(const ConfigOptions& opts, const std::string& value, TestCustomizable** result); #endif // ROCKSDB_LITE bool IsInstanceOf(const std::string& name) const override { if (name == kClassName()) { return true; } else { return Customizable::IsInstanceOf(name); } } protected: const std::string name_; }; struct AOptions { static const char* kName() { return "A"; } int i = 0; bool b = false; }; static std::unordered_map a_option_info = { #ifndef ROCKSDB_LITE {"int", {offsetof(struct AOptions, i), OptionType::kInt, OptionVerificationType::kNormal, OptionTypeFlags::kMutable}}, {"bool", {offsetof(struct AOptions, b), OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, #endif // ROCKSDB_LITE }; class ACustomizable : public TestCustomizable { public: explicit ACustomizable(const std::string& id) : TestCustomizable("A"), id_(id) { RegisterOptions(&opts_, &a_option_info); } std::string GetId() const override { return id_; } static const char* kClassName() { return "A"; } private: AOptions opts_; const std::string id_; }; struct BOptions { std::string s; bool b = false; }; static std::unordered_map b_option_info = { #ifndef ROCKSDB_LITE {"string", {offsetof(struct BOptions, s), OptionType::kString, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, {"bool", {offsetof(struct BOptions, b), OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, #endif // ROCKSDB_LITE }; class BCustomizable : public TestCustomizable { private: public: explicit BCustomizable(const std::string& name) : TestCustomizable(name) { RegisterOptions(name, &opts_, &b_option_info); } static const char* kClassName() { return "B"; } private: BOptions opts_; }; #ifndef ROCKSDB_LITE static bool LoadSharedB(const std::string& id, std::shared_ptr* result) { if (id == "B") { result->reset(new BCustomizable(id)); return true; } else if (id.empty()) { result->reset(); return true; } else { return false; } } static int A_count = 0; static int RegisterCustomTestObjects(ObjectLibrary& library, const std::string& /*arg*/) { library.AddFactory( ObjectLibrary::PatternEntry("A", true).AddSeparator("_"), [](const std::string& name, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new ACustomizable(name)); A_count++; return guard->get(); }); library.AddFactory( "S", [](const std::string& name, std::unique_ptr* /* guard */, std::string* /* msg */) { return new BCustomizable(name); }); size_t num_types; return static_cast(library.GetFactoryCount(&num_types)); } #endif // ROCKSDB_LITE struct SimpleOptions { static const char* kName() { return "simple"; } bool b = true; std::unique_ptr cu; std::shared_ptr cs; TestCustomizable* cp = nullptr; }; static std::unordered_map simple_option_info = { #ifndef ROCKSDB_LITE {"bool", {offsetof(struct SimpleOptions, b), OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, {"unique", OptionTypeInfo::AsCustomUniquePtr( offsetof(struct SimpleOptions, cu), OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}, {"shared", OptionTypeInfo::AsCustomSharedPtr( offsetof(struct SimpleOptions, cs), OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}, {"pointer", OptionTypeInfo::AsCustomRawPtr( offsetof(struct SimpleOptions, cp), OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}, #endif // ROCKSDB_LITE }; class SimpleConfigurable : public Configurable { private: SimpleOptions simple_; public: SimpleConfigurable() { RegisterOptions(&simple_, &simple_option_info); } explicit SimpleConfigurable( const std::unordered_map* map) { RegisterOptions(&simple_, map); } }; #ifndef ROCKSDB_LITE static void GetMapFromProperties( const std::string& props, std::unordered_map* map) { std::istringstream iss(props); std::unordered_map copy_map; std::string line; map->clear(); for (int line_num = 0; std::getline(iss, line); line_num++) { std::string name; std::string value; ASSERT_OK( RocksDBOptionsParser::ParseStatement(&name, &value, line, line_num)); (*map)[name] = value; } } #endif // ROCKSDB_LITE } // namespace #ifndef ROCKSDB_LITE Status TestCustomizable::CreateFromString( const ConfigOptions& config_options, const std::string& value, std::shared_ptr* result) { return LoadSharedObject(config_options, value, LoadSharedB, result); } Status TestCustomizable::CreateFromString( const ConfigOptions& config_options, const std::string& value, std::unique_ptr* result) { return LoadUniqueObject( config_options, value, [](const std::string& id, std::unique_ptr* u) { if (id == "B") { u->reset(new BCustomizable(id)); return true; } else if (id.empty()) { u->reset(); return true; } else { return false; } }, result); } Status TestCustomizable::CreateFromString(const ConfigOptions& config_options, const std::string& value, TestCustomizable** result) { return LoadStaticObject( config_options, value, [](const std::string& id, TestCustomizable** ptr) { if (id == "B") { *ptr = new BCustomizable(id); return true; } else if (id.empty()) { *ptr = nullptr; return true; } else { return false; } }, result); } #endif // ROCKSDB_LITE class CustomizableTest : public testing::Test { public: CustomizableTest() { config_options_.invoke_prepare_options = false; #ifndef ROCKSDB_LITE // GetOptionsFromMap is not supported in ROCKSDB_LITE config_options_.registry->AddLibrary("CustomizableTest", RegisterCustomTestObjects, ""); #endif // ROCKSDB_LITE } ConfigOptions config_options_; }; #ifndef ROCKSDB_LITE // GetOptionsFromMap is not supported in ROCKSDB_LITE // Tests that a Customizable can be created by: // - a simple name // - a XXX.id option // - a property with a name TEST_F(CustomizableTest, CreateByNameTest) { ObjectLibrary::Default()->AddFactory( ObjectLibrary::PatternEntry("TEST", false).AddSeparator("_"), [](const std::string& name, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new TestCustomizable(name)); return guard->get(); }); std::unique_ptr configurable(new SimpleConfigurable()); SimpleOptions* simple = configurable->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_OK( configurable->ConfigureFromString(config_options_, "unique={id=TEST_1}")); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "TEST_1"); ASSERT_OK( configurable->ConfigureFromString(config_options_, "unique.id=TEST_2")); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "TEST_2"); ASSERT_OK( configurable->ConfigureFromString(config_options_, "unique=TEST_3")); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "TEST_3"); } TEST_F(CustomizableTest, ToStringTest) { std::unique_ptr custom(new TestCustomizable("test")); ASSERT_EQ(custom->ToString(config_options_), "test"); } TEST_F(CustomizableTest, SimpleConfigureTest) { std::unordered_map opt_map = { {"unique", "id=A;int=1;bool=true"}, {"shared", "id=B;string=s"}, }; std::unique_ptr configurable(new SimpleConfigurable()); ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); SimpleOptions* simple = configurable->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "A"); std::string opt_str; std::string mismatch; ASSERT_OK(configurable->GetOptionString(config_options_, &opt_str)); std::unique_ptr copy(new SimpleConfigurable()); ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); ASSERT_TRUE( configurable->AreEquivalent(config_options_, copy.get(), &mismatch)); } TEST_F(CustomizableTest, ConfigureFromPropsTest) { std::unordered_map opt_map = { {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, {"shared.id", "B"}, {"shared.B.string", "s"}, }; std::unique_ptr configurable(new SimpleConfigurable()); ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); SimpleOptions* simple = configurable->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "A"); std::string opt_str; std::string mismatch; config_options_.delimiter = "\n"; std::unordered_map props; ASSERT_OK(configurable->GetOptionString(config_options_, &opt_str)); GetMapFromProperties(opt_str, &props); std::unique_ptr copy(new SimpleConfigurable()); ASSERT_OK(copy->ConfigureFromMap(config_options_, props)); ASSERT_TRUE( configurable->AreEquivalent(config_options_, copy.get(), &mismatch)); } TEST_F(CustomizableTest, ConfigureFromShortTest) { std::unordered_map opt_map = { {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, {"shared.id", "B"}, {"shared.B.string", "s"}, }; std::unique_ptr configurable(new SimpleConfigurable()); ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); SimpleOptions* simple = configurable->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), "A"); } TEST_F(CustomizableTest, AreEquivalentOptionsTest) { std::unordered_map opt_map = { {"unique", "id=A;int=1;bool=true"}, {"shared", "id=A;int=1;bool=true"}, }; std::string mismatch; ConfigOptions config_options = config_options_; std::unique_ptr c1(new SimpleConfigurable()); std::unique_ptr c2(new SimpleConfigurable()); ASSERT_OK(c1->ConfigureFromMap(config_options, opt_map)); ASSERT_OK(c2->ConfigureFromMap(config_options, opt_map)); ASSERT_TRUE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); SimpleOptions* simple = c1->GetOptions(); ASSERT_TRUE( simple->cu->AreEquivalent(config_options, simple->cs.get(), &mismatch)); ASSERT_OK(simple->cu->ConfigureOption(config_options, "int", "2")); ASSERT_FALSE( simple->cu->AreEquivalent(config_options, simple->cs.get(), &mismatch)); ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); ConfigOptions loosely = config_options; loosely.sanity_level = ConfigOptions::kSanityLevelLooselyCompatible; ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); ASSERT_TRUE(simple->cu->AreEquivalent(loosely, simple->cs.get(), &mismatch)); ASSERT_OK(c1->ConfigureOption(config_options, "shared", "id=B;string=3")); ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); ASSERT_FALSE(simple->cs->AreEquivalent(loosely, simple->cu.get(), &mismatch)); simple->cs.reset(); ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); } // Tests that we can initialize a customizable from its options TEST_F(CustomizableTest, ConfigureStandaloneCustomTest) { std::unique_ptr base, copy; const auto& registry = config_options_.registry; ASSERT_OK(registry->NewUniqueObject("A", &base)); ASSERT_OK(registry->NewUniqueObject("A", ©)); ASSERT_OK(base->ConfigureFromString(config_options_, "int=33;bool=true")); std::string opt_str; std::string mismatch; ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); } // Tests that we fail appropriately if the pattern is not registered TEST_F(CustomizableTest, BadNameTest) { config_options_.ignore_unsupported_options = false; std::unique_ptr c1(new SimpleConfigurable()); ASSERT_NOK( c1->ConfigureFromString(config_options_, "unique.shared.id=bad name")); config_options_.ignore_unsupported_options = true; ASSERT_OK( c1->ConfigureFromString(config_options_, "unique.shared.id=bad name")); } // Tests that we fail appropriately if a bad option is passed to the underlying // configurable TEST_F(CustomizableTest, BadOptionTest) { std::unique_ptr c1(new SimpleConfigurable()); ConfigOptions ignore = config_options_; ignore.ignore_unknown_options = true; ASSERT_NOK(c1->ConfigureFromString(config_options_, "A.int=11")); ASSERT_NOK(c1->ConfigureFromString(config_options_, "shared={id=B;int=1}")); ASSERT_OK(c1->ConfigureFromString(ignore, "shared={id=A;string=s}")); ASSERT_NOK(c1->ConfigureFromString(config_options_, "B.int=11")); ASSERT_OK(c1->ConfigureFromString(ignore, "B.int=11")); ASSERT_NOK(c1->ConfigureFromString(config_options_, "A.string=s")); ASSERT_OK(c1->ConfigureFromString(ignore, "A.string=s")); // Test as detached ASSERT_NOK( c1->ConfigureFromString(config_options_, "shared.id=A;A.string=b}")); ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=A;A.string=s}")); } TEST_F(CustomizableTest, FailingFactoryTest) { std::shared_ptr registry = ObjectRegistry::NewInstance(); std::unique_ptr c1(new SimpleConfigurable()); ConfigOptions ignore = config_options_; Status s; ignore.registry->AddLibrary("failing")->AddFactory( "failing", [](const std::string& /*uri*/, std::unique_ptr* /*guard */, std::string* errmsg) { *errmsg = "Bad Factory"; return nullptr; }); // If we are ignoring unknown and unsupported options, will see // different errors for failing versus missing ignore.ignore_unknown_options = false; ignore.ignore_unsupported_options = false; s = c1->ConfigureFromString(ignore, "shared.id=failing"); ASSERT_TRUE(s.IsInvalidArgument()); s = c1->ConfigureFromString(ignore, "unique.id=failing"); ASSERT_TRUE(s.IsInvalidArgument()); s = c1->ConfigureFromString(ignore, "shared.id=missing"); ASSERT_TRUE(s.IsNotSupported()); s = c1->ConfigureFromString(ignore, "unique.id=missing"); ASSERT_TRUE(s.IsNotSupported()); // If we are ignoring unsupported options, will see // errors for failing but not missing ignore.ignore_unknown_options = false; ignore.ignore_unsupported_options = true; s = c1->ConfigureFromString(ignore, "shared.id=failing"); ASSERT_TRUE(s.IsInvalidArgument()); s = c1->ConfigureFromString(ignore, "unique.id=failing"); ASSERT_TRUE(s.IsInvalidArgument()); ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=missing")); ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=missing")); // If we are ignoring unknown options, will see no errors // for failing or missing ignore.ignore_unknown_options = true; ignore.ignore_unsupported_options = false; ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=failing")); ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=failing")); ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=missing")); ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=missing")); } // Tests that different IDs lead to different objects TEST_F(CustomizableTest, UniqueIdTest) { std::unique_ptr base(new SimpleConfigurable()); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=A_1;int=1;bool=true}")); SimpleOptions* simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(simple->cu->GetId(), std::string("A_1")); std::string opt_str; std::string mismatch; ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); std::unique_ptr copy(new SimpleConfigurable()); ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=A_2;int=1;bool=true}")); ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_EQ(simple->cu->GetId(), std::string("A_2")); } TEST_F(CustomizableTest, IsInstanceOfTest) { std::shared_ptr tc = std::make_shared("A_1"); ASSERT_EQ(tc->GetId(), std::string("A_1")); ASSERT_TRUE(tc->IsInstanceOf("A")); ASSERT_TRUE(tc->IsInstanceOf("TestCustomizable")); ASSERT_FALSE(tc->IsInstanceOf("B")); ASSERT_FALSE(tc->IsInstanceOf("A_1")); ASSERT_EQ(tc->CheckedCast(), tc.get()); ASSERT_EQ(tc->CheckedCast(), tc.get()); ASSERT_EQ(tc->CheckedCast(), nullptr); tc.reset(new BCustomizable("B")); ASSERT_TRUE(tc->IsInstanceOf("B")); ASSERT_TRUE(tc->IsInstanceOf("TestCustomizable")); ASSERT_FALSE(tc->IsInstanceOf("A")); ASSERT_EQ(tc->CheckedCast(), tc.get()); ASSERT_EQ(tc->CheckedCast(), tc.get()); ASSERT_EQ(tc->CheckedCast(), nullptr); } TEST_F(CustomizableTest, PrepareOptionsTest) { static std::unordered_map p_option_info = { #ifndef ROCKSDB_LITE {"can_prepare", {0, OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, #endif // ROCKSDB_LITE }; class PrepareCustomizable : public TestCustomizable { public: bool can_prepare_ = true; PrepareCustomizable() : TestCustomizable("P") { RegisterOptions("Prepare", &can_prepare_, &p_option_info); } Status PrepareOptions(const ConfigOptions& opts) override { if (!can_prepare_) { return Status::InvalidArgument("Cannot Prepare"); } else { return TestCustomizable::PrepareOptions(opts); } } }; ObjectLibrary::Default()->AddFactory( "P", [](const std::string& /*name*/, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new PrepareCustomizable()); return guard->get(); }); std::unique_ptr base(new SimpleConfigurable()); ConfigOptions prepared(config_options_); prepared.invoke_prepare_options = true; ASSERT_OK(base->ConfigureFromString( prepared, "unique=A_1; shared={id=B;string=s}; pointer.id=S")); SimpleOptions* simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_NE(simple->cs, nullptr); ASSERT_NE(simple->cp, nullptr); delete simple->cp; base.reset(new SimpleConfigurable()); ASSERT_OK(base->ConfigureFromString( config_options_, "unique=A_1; shared={id=B;string=s}; pointer.id=S")); simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_NE(simple->cs, nullptr); ASSERT_NE(simple->cp, nullptr); ASSERT_OK(base->PrepareOptions(config_options_)); delete simple->cp; base.reset(new SimpleConfigurable()); simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NOK( base->ConfigureFromString(prepared, "unique={id=P; can_prepare=false}")); ASSERT_EQ(simple->cu, nullptr); ASSERT_OK( base->ConfigureFromString(prepared, "unique={id=P; can_prepare=true}")); ASSERT_NE(simple->cu, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=P; can_prepare=true}")); ASSERT_NE(simple->cu, nullptr); ASSERT_OK(simple->cu->PrepareOptions(prepared)); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=P; can_prepare=false}")); ASSERT_NE(simple->cu, nullptr); ASSERT_NOK(simple->cu->PrepareOptions(prepared)); } namespace { static std::unordered_map inner_option_info = { #ifndef ROCKSDB_LITE {"inner", OptionTypeInfo::AsCustomSharedPtr( 0, OptionVerificationType::kNormal, OptionTypeFlags::kStringNameOnly)} #endif // ROCKSDB_LITE }; struct InnerOptions { static const char* kName() { return "InnerOptions"; } std::shared_ptr inner; }; class InnerCustomizable : public Customizable { public: explicit InnerCustomizable(const std::shared_ptr& w) { iopts_.inner = w; RegisterOptions(&iopts_, &inner_option_info); } static const char* kClassName() { return "Inner"; } const char* Name() const override { return kClassName(); } bool IsInstanceOf(const std::string& name) const override { if (name == kClassName()) { return true; } else { return Customizable::IsInstanceOf(name); } } protected: const Customizable* Inner() const override { return iopts_.inner.get(); } private: InnerOptions iopts_; }; struct WrappedOptions1 { static const char* kName() { return "WrappedOptions1"; } int i = 42; }; class WrappedCustomizable1 : public InnerCustomizable { public: explicit WrappedCustomizable1(const std::shared_ptr& w) : InnerCustomizable(w) { RegisterOptions(&wopts_, nullptr); } const char* Name() const override { return kClassName(); } static const char* kClassName() { return "Wrapped1"; } private: WrappedOptions1 wopts_; }; struct WrappedOptions2 { static const char* kName() { return "WrappedOptions2"; } std::string s = "42"; }; class WrappedCustomizable2 : public InnerCustomizable { public: explicit WrappedCustomizable2(const std::shared_ptr& w) : InnerCustomizable(w) {} const void* GetOptionsPtr(const std::string& name) const override { if (name == WrappedOptions2::kName()) { return &wopts_; } else { return InnerCustomizable::GetOptionsPtr(name); } } const char* Name() const override { return kClassName(); } static const char* kClassName() { return "Wrapped2"; } private: WrappedOptions2 wopts_; }; } // namespace TEST_F(CustomizableTest, WrappedInnerTest) { std::shared_ptr ac = std::make_shared("A"); ASSERT_TRUE(ac->IsInstanceOf("A")); ASSERT_TRUE(ac->IsInstanceOf("TestCustomizable")); ASSERT_EQ(ac->CheckedCast(), ac.get()); ASSERT_EQ(ac->CheckedCast(), nullptr); ASSERT_EQ(ac->CheckedCast(), nullptr); ASSERT_EQ(ac->CheckedCast(), nullptr); std::shared_ptr wc1 = std::make_shared(ac); ASSERT_TRUE(wc1->IsInstanceOf(WrappedCustomizable1::kClassName())); ASSERT_EQ(wc1->CheckedCast(), wc1.get()); ASSERT_EQ(wc1->CheckedCast(), nullptr); ASSERT_EQ(wc1->CheckedCast(), wc1.get()); ASSERT_EQ(wc1->CheckedCast(), ac.get()); std::shared_ptr wc2 = std::make_shared(wc1); ASSERT_TRUE(wc2->IsInstanceOf(WrappedCustomizable2::kClassName())); ASSERT_EQ(wc2->CheckedCast(), wc2.get()); ASSERT_EQ(wc2->CheckedCast(), wc1.get()); ASSERT_EQ(wc2->CheckedCast(), wc2.get()); ASSERT_EQ(wc2->CheckedCast(), ac.get()); } TEST_F(CustomizableTest, CustomizableInnerTest) { std::shared_ptr c = std::make_shared(std::make_shared("a")); std::shared_ptr wc1 = std::make_shared(c); std::shared_ptr wc2 = std::make_shared(c); auto inner = c->GetOptions(); ASSERT_NE(inner, nullptr); auto aopts = c->GetOptions(); ASSERT_NE(aopts, nullptr); ASSERT_EQ(aopts, wc1->GetOptions()); ASSERT_EQ(aopts, wc2->GetOptions()); auto w1opts = wc1->GetOptions(); ASSERT_NE(w1opts, nullptr); ASSERT_EQ(c->GetOptions(), nullptr); ASSERT_EQ(wc2->GetOptions(), nullptr); auto w2opts = wc2->GetOptions(); ASSERT_NE(w2opts, nullptr); ASSERT_EQ(c->GetOptions(), nullptr); ASSERT_EQ(wc1->GetOptions(), nullptr); } TEST_F(CustomizableTest, CopyObjectTest) { class CopyCustomizable : public Customizable { public: CopyCustomizable() : prepared_(0), validated_(0) {} const char* Name() const override { return "CopyCustomizable"; } Status PrepareOptions(const ConfigOptions& options) override { prepared_++; return Customizable::PrepareOptions(options); } Status ValidateOptions(const DBOptions& db_opts, const ColumnFamilyOptions& cf_opts) const override { validated_++; return Customizable::ValidateOptions(db_opts, cf_opts); } int prepared_; mutable int validated_; }; CopyCustomizable c1; ConfigOptions config_options; Options options; ASSERT_OK(c1.PrepareOptions(config_options)); ASSERT_OK(c1.ValidateOptions(options, options)); ASSERT_EQ(c1.prepared_, 1); ASSERT_EQ(c1.validated_, 1); CopyCustomizable c2 = c1; ASSERT_OK(c1.PrepareOptions(config_options)); ASSERT_OK(c1.ValidateOptions(options, options)); ASSERT_EQ(c2.prepared_, 1); ASSERT_EQ(c2.validated_, 1); ASSERT_EQ(c1.prepared_, 2); ASSERT_EQ(c1.validated_, 2); } TEST_F(CustomizableTest, TestStringDepth) { ConfigOptions shallow = config_options_; std::unique_ptr c( new InnerCustomizable(std::make_shared("a"))); std::string opt_str; shallow.depth = ConfigOptions::Depth::kDepthShallow; ASSERT_OK(c->GetOptionString(shallow, &opt_str)); ASSERT_EQ(opt_str, "inner=a;"); shallow.depth = ConfigOptions::Depth::kDepthDetailed; ASSERT_OK(c->GetOptionString(shallow, &opt_str)); ASSERT_NE(opt_str, "inner=a;"); } // Tests that we only get a new customizable when it changes TEST_F(CustomizableTest, NewUniqueCustomizableTest) { std::unique_ptr base(new SimpleConfigurable()); A_count = 0; ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=A_1;int=1;bool=true}")); SimpleOptions* simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_NE(simple->cu, nullptr); ASSERT_EQ(A_count, 1); // Created one A ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=A_1;int=1;bool=false}")); ASSERT_EQ(A_count, 2); // Create another A_1 ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=}")); ASSERT_EQ(simple->cu, nullptr); ASSERT_EQ(A_count, 2); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=A_2;int=1;bool=false}")); ASSERT_EQ(A_count, 3); // Created another A ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=")); ASSERT_EQ(simple->cu, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "unique=nullptr")); ASSERT_EQ(simple->cu, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=nullptr")); ASSERT_EQ(simple->cu, nullptr); ASSERT_EQ(A_count, 3); } TEST_F(CustomizableTest, NewEmptyUniqueTest) { std::unique_ptr base(new SimpleConfigurable()); SimpleOptions* simple = base->GetOptions(); ASSERT_EQ(simple->cu, nullptr); simple->cu.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=}")); ASSERT_EQ(simple->cu, nullptr); simple->cu.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=nullptr}")); ASSERT_EQ(simple->cu, nullptr); simple->cu.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=")); ASSERT_EQ(simple->cu, nullptr); simple->cu.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "unique=nullptr")); ASSERT_EQ(simple->cu, nullptr); simple->cu.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=nullptr")); ASSERT_EQ(simple->cu, nullptr); } TEST_F(CustomizableTest, NewEmptySharedTest) { std::unique_ptr base(new SimpleConfigurable()); SimpleOptions* simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_EQ(simple->cs, nullptr); simple->cs.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "shared={id=}")); ASSERT_NE(simple, nullptr); ASSERT_EQ(simple->cs, nullptr); simple->cs.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "shared={id=nullptr}")); ASSERT_EQ(simple->cs, nullptr); simple->cs.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "shared.id=")); ASSERT_EQ(simple->cs, nullptr); simple->cs.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "shared.id=nullptr")); ASSERT_EQ(simple->cs, nullptr); simple->cs.reset(new BCustomizable("B")); ASSERT_OK(base->ConfigureFromString(config_options_, "shared=nullptr")); ASSERT_EQ(simple->cs, nullptr); } TEST_F(CustomizableTest, NewEmptyStaticTest) { std::unique_ptr base(new SimpleConfigurable()); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer={id=}")); SimpleOptions* simple = base->GetOptions(); ASSERT_NE(simple, nullptr); ASSERT_EQ(simple->cp, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer={id=nullptr}")); ASSERT_EQ(simple->cp, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer=")); ASSERT_EQ(simple->cp, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer=nullptr")); ASSERT_EQ(simple->cp, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer.id=")); ASSERT_EQ(simple->cp, nullptr); ASSERT_OK(base->ConfigureFromString(config_options_, "pointer.id=nullptr")); ASSERT_EQ(simple->cp, nullptr); } namespace { #ifndef ROCKSDB_LITE static std::unordered_map vector_option_info = { {"vector", OptionTypeInfo::Vector>( 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone, OptionTypeInfo::AsCustomSharedPtr( 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone))}, }; class VectorConfigurable : public SimpleConfigurable { public: VectorConfigurable() { RegisterOptions("vector", &cv, &vector_option_info); } std::vector> cv; }; } // namespace TEST_F(CustomizableTest, VectorConfigTest) { VectorConfigurable orig, copy; std::shared_ptr c1, c2; ASSERT_OK(TestCustomizable::CreateFromString(config_options_, "A", &c1)); ASSERT_OK(TestCustomizable::CreateFromString(config_options_, "B", &c2)); orig.cv.push_back(c1); orig.cv.push_back(c2); ASSERT_OK(orig.ConfigureFromString(config_options_, "unique=A2")); std::string opt_str, mismatch; ASSERT_OK(orig.GetOptionString(config_options_, &opt_str)); ASSERT_OK(copy.ConfigureFromString(config_options_, opt_str)); ASSERT_TRUE(orig.AreEquivalent(config_options_, ©, &mismatch)); } TEST_F(CustomizableTest, NoNameTest) { // If Customizables are created without names, they are not // part of the serialization (since they cannot be recreated) VectorConfigurable orig, copy; auto sopts = orig.GetOptions(); auto copts = copy.GetOptions(); sopts->cu.reset(new ACustomizable("")); orig.cv.push_back(std::make_shared("")); orig.cv.push_back(std::make_shared("A_1")); std::string opt_str, mismatch; ASSERT_OK(orig.GetOptionString(config_options_, &opt_str)); ASSERT_OK(copy.ConfigureFromString(config_options_, opt_str)); ASSERT_EQ(copy.cv.size(), 1U); ASSERT_EQ(copy.cv[0]->GetId(), "A_1"); ASSERT_EQ(copts->cu, nullptr); } #endif // ROCKSDB_LITE TEST_F(CustomizableTest, IgnoreUnknownObjects) { ConfigOptions ignore = config_options_; std::shared_ptr shared; std::unique_ptr unique; TestCustomizable* pointer = nullptr; ignore.ignore_unsupported_options = false; ASSERT_NOK( LoadSharedObject(ignore, "Unknown", nullptr, &shared)); ASSERT_NOK( LoadUniqueObject(ignore, "Unknown", nullptr, &unique)); ASSERT_NOK( LoadStaticObject(ignore, "Unknown", nullptr, &pointer)); ASSERT_EQ(shared.get(), nullptr); ASSERT_EQ(unique.get(), nullptr); ASSERT_EQ(pointer, nullptr); ignore.ignore_unsupported_options = true; ASSERT_OK( LoadSharedObject(ignore, "Unknown", nullptr, &shared)); ASSERT_OK( LoadUniqueObject(ignore, "Unknown", nullptr, &unique)); ASSERT_OK( LoadStaticObject(ignore, "Unknown", nullptr, &pointer)); ASSERT_EQ(shared.get(), nullptr); ASSERT_EQ(unique.get(), nullptr); ASSERT_EQ(pointer, nullptr); ASSERT_OK(LoadSharedObject(ignore, "id=Unknown", nullptr, &shared)); ASSERT_OK(LoadUniqueObject(ignore, "id=Unknown", nullptr, &unique)); ASSERT_OK(LoadStaticObject(ignore, "id=Unknown", nullptr, &pointer)); ASSERT_EQ(shared.get(), nullptr); ASSERT_EQ(unique.get(), nullptr); ASSERT_EQ(pointer, nullptr); ASSERT_OK(LoadSharedObject(ignore, "id=Unknown;option=bad", nullptr, &shared)); ASSERT_OK(LoadUniqueObject(ignore, "id=Unknown;option=bad", nullptr, &unique)); ASSERT_OK(LoadStaticObject(ignore, "id=Unknown;option=bad", nullptr, &pointer)); ASSERT_EQ(shared.get(), nullptr); ASSERT_EQ(unique.get(), nullptr); ASSERT_EQ(pointer, nullptr); } TEST_F(CustomizableTest, FactoryFunctionTest) { std::shared_ptr shared; std::unique_ptr unique; TestCustomizable* pointer = nullptr; ConfigOptions ignore = config_options_; ignore.ignore_unsupported_options = false; ASSERT_OK(TestCustomizable::CreateFromString(ignore, "B", &shared)); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "B", &unique)); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "B", &pointer)); ASSERT_NE(shared.get(), nullptr); ASSERT_NE(unique.get(), nullptr); ASSERT_NE(pointer, nullptr); delete pointer; pointer = nullptr; ASSERT_OK(TestCustomizable::CreateFromString(ignore, "id=", &shared)); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "id=", &unique)); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "id=", &pointer)); ASSERT_EQ(shared.get(), nullptr); ASSERT_EQ(unique.get(), nullptr); ASSERT_EQ(pointer, nullptr); ASSERT_NOK(TestCustomizable::CreateFromString(ignore, "option=bad", &shared)); ASSERT_NOK(TestCustomizable::CreateFromString(ignore, "option=bad", &unique)); ASSERT_NOK( TestCustomizable::CreateFromString(ignore, "option=bad", &pointer)); ASSERT_EQ(pointer, nullptr); } TEST_F(CustomizableTest, URLFactoryTest) { std::unique_ptr unique; config_options_.registry->AddLibrary("URL")->AddFactory( ObjectLibrary::PatternEntry("Z", false).AddSeparator(""), [](const std::string& name, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new TestCustomizable(name)); return guard->get(); }); ConfigOptions ignore = config_options_; ignore.ignore_unsupported_options = false; ignore.ignore_unsupported_options = false; ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1;x=y", &unique)); ASSERT_NE(unique, nullptr); ASSERT_EQ(unique->GetId(), "Z=1;x=y"); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z;x=y", &unique)); ASSERT_NE(unique, nullptr); ASSERT_EQ(unique->GetId(), "Z;x=y"); unique.reset(); ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1?x=y", &unique)); ASSERT_NE(unique, nullptr); ASSERT_EQ(unique->GetId(), "Z=1?x=y"); } TEST_F(CustomizableTest, MutableOptionsTest) { static std::unordered_map mutable_option_info = { {"mutable", OptionTypeInfo::AsCustomSharedPtr( 0, OptionVerificationType::kNormal, OptionTypeFlags::kMutable)}}; static std::unordered_map immutable_option_info = {{"immutable", OptionTypeInfo::AsCustomSharedPtr( 0, OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}}; class MutableCustomizable : public Customizable { private: std::shared_ptr mutable_; std::shared_ptr immutable_; public: MutableCustomizable() { RegisterOptions("mutable", &mutable_, &mutable_option_info); RegisterOptions("immutable", &immutable_, &immutable_option_info); } const char* Name() const override { return "MutableCustomizable"; } }; MutableCustomizable mc, mc2; std::string mismatch; std::string opt_str; ConfigOptions options = config_options_; ASSERT_OK(mc.ConfigureOption(options, "mutable", "{id=B;}")); options.mutable_options_only = true; ASSERT_OK(mc.GetOptionString(options, &opt_str)); ASSERT_OK(mc2.ConfigureFromString(options, opt_str)); ASSERT_TRUE(mc.AreEquivalent(options, &mc2, &mismatch)); options.mutable_options_only = false; ASSERT_OK(mc.ConfigureOption(options, "immutable", "{id=A; int=10}")); auto* mm = mc.GetOptions>("mutable"); auto* im = mc.GetOptions>("immutable"); ASSERT_NE(mm, nullptr); ASSERT_NE(mm->get(), nullptr); ASSERT_NE(im, nullptr); ASSERT_NE(im->get(), nullptr); // Now only deal with mutable options options.mutable_options_only = true; // Setting nested immutable customizable options fails ASSERT_NOK(mc.ConfigureOption(options, "immutable", "{id=B;}")); ASSERT_NOK(mc.ConfigureOption(options, "immutable.id", "B")); ASSERT_NOK(mc.ConfigureOption(options, "immutable.bool", "true")); ASSERT_NOK(mc.ConfigureOption(options, "immutable", "bool=true")); ASSERT_NOK(mc.ConfigureOption(options, "immutable", "{int=11;bool=true}")); auto* im_a = im->get()->GetOptions("A"); ASSERT_NE(im_a, nullptr); ASSERT_EQ(im_a->i, 10); ASSERT_EQ(im_a->b, false); // Setting nested mutable customizable options succeeds but the object did not // change ASSERT_OK(mc.ConfigureOption(options, "immutable.int", "11")); ASSERT_EQ(im_a->i, 11); ASSERT_EQ(im_a, im->get()->GetOptions("A")); // The mutable configurable itself can be changed ASSERT_OK(mc.ConfigureOption(options, "mutable.id", "A")); ASSERT_OK(mc.ConfigureOption(options, "mutable", "A")); ASSERT_OK(mc.ConfigureOption(options, "mutable", "{id=A}")); ASSERT_OK(mc.ConfigureOption(options, "mutable", "{bool=true}")); // The Nested options in the mutable object can be changed ASSERT_OK(mc.ConfigureOption(options, "mutable", "{bool=true}")); auto* mm_a = mm->get()->GetOptions("A"); ASSERT_EQ(mm_a->b, true); ASSERT_OK(mc.ConfigureOption(options, "mutable", "{int=22;bool=false}")); mm_a = mm->get()->GetOptions("A"); ASSERT_EQ(mm_a->i, 22); ASSERT_EQ(mm_a->b, false); // Only the mutable options should get serialized options.mutable_options_only = false; ASSERT_OK(mc.GetOptionString(options, &opt_str)); ASSERT_OK(mc.ConfigureOption(options, "immutable", "{id=B;}")); options.mutable_options_only = true; ASSERT_OK(mc.GetOptionString(options, &opt_str)); ASSERT_OK(mc2.ConfigureFromString(options, opt_str)); ASSERT_TRUE(mc.AreEquivalent(options, &mc2, &mismatch)); options.mutable_options_only = false; ASSERT_FALSE(mc.AreEquivalent(options, &mc2, &mismatch)); ASSERT_EQ(mismatch, "immutable"); } TEST_F(CustomizableTest, CustomManagedObjects) { std::shared_ptr object1, object2; ASSERT_OK(LoadManagedObject( config_options_, "id=A_1;int=1;bool=true", &object1)); ASSERT_NE(object1, nullptr); ASSERT_OK( LoadManagedObject(config_options_, "A_1", &object2)); ASSERT_EQ(object1, object2); auto* opts = object2->GetOptions("A"); ASSERT_NE(opts, nullptr); ASSERT_EQ(opts->i, 1); ASSERT_EQ(opts->b, true); ASSERT_OK( LoadManagedObject(config_options_, "A_2", &object2)); ASSERT_NE(object1, object2); object1.reset(); ASSERT_OK(LoadManagedObject( config_options_, "id=A_1;int=2;bool=false", &object1)); opts = object1->GetOptions("A"); ASSERT_NE(opts, nullptr); ASSERT_EQ(opts->i, 2); ASSERT_EQ(opts->b, false); } TEST_F(CustomizableTest, CreateManagedObjects) { class ManagedCustomizable : public Customizable { public: static const char* Type() { return "ManagedCustomizable"; } static const char* kClassName() { return "Managed"; } const char* Name() const override { return kClassName(); } std::string GetId() const override { return id_; } ManagedCustomizable() { id_ = GenerateIndividualId(); } static Status CreateFromString( const ConfigOptions& opts, const std::string& value, std::shared_ptr* result) { return LoadManagedObject(opts, value, result); } private: std::string id_; }; config_options_.registry->AddLibrary("Managed") ->AddFactory( ObjectLibrary::PatternEntry::AsIndividualId( ManagedCustomizable::kClassName()), [](const std::string& /*name*/, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new ManagedCustomizable()); return guard->get(); }); std::shared_ptr mc1, mc2, mc3, obj; // Create a "deadbeef" customizable std::string deadbeef = std::string(ManagedCustomizable::kClassName()) + "@0xdeadbeef#0001"; ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, deadbeef, &mc1)); // Create an object with the base/class name ASSERT_OK(ManagedCustomizable::CreateFromString( config_options_, ManagedCustomizable::kClassName(), &mc2)); // Creating another with the base name returns a different object ASSERT_OK(ManagedCustomizable::CreateFromString( config_options_, ManagedCustomizable::kClassName(), &mc3)); // At this point, there should be 4 managed objects (deadbeef, mc1, 2, and 3) std::vector> objects; ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); ASSERT_EQ(objects.size(), 4U); objects.clear(); // Three separate object, none of them equal ASSERT_NE(mc1, mc2); ASSERT_NE(mc1, mc3); ASSERT_NE(mc2, mc3); // Creating another object with "deadbeef" object ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); ASSERT_EQ(mc1, obj); // Create another with the IDs of the instances ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc1->GetId(), &obj)); ASSERT_EQ(mc1, obj); ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc2->GetId(), &obj)); ASSERT_EQ(mc2, obj); ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc3->GetId(), &obj)); ASSERT_EQ(mc3, obj); // Now get rid of deadbeef. 2 Objects left (m2+m3) mc1.reset(); ASSERT_EQ( config_options_.registry->GetManagedObject(deadbeef), nullptr); ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); ASSERT_EQ(objects.size(), 2U); objects.clear(); // Associate deadbeef with #2 ASSERT_OK(config_options_.registry->SetManagedObject(deadbeef, mc2)); ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); ASSERT_EQ(mc2, obj); obj.reset(); // Get the ID of mc2 and then reset it. 1 Object left std::string mc2id = mc2->GetId(); mc2.reset(); ASSERT_EQ( config_options_.registry->GetManagedObject(mc2id), nullptr); ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); ASSERT_EQ(objects.size(), 1U); objects.clear(); // Create another object with the old mc2id. ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, mc2id, &mc2)); ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, mc2id, &obj)); ASSERT_EQ(mc2, obj); // For good measure, create another deadbeef object ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, deadbeef, &mc1)); ASSERT_OK( ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); ASSERT_EQ(mc1, obj); } #endif // !ROCKSDB_LITE namespace { class TestSecondaryCache : public SecondaryCache { public: static const char* kClassName() { return "Test"; } const char* Name() const override { return kClassName(); } Status Insert(const Slice& /*key*/, void* /*value*/, const Cache::CacheItemHelper* /*helper*/) override { return Status::NotSupported(); } std::unique_ptr Lookup( const Slice& /*key*/, const Cache::CreateCallback& /*create_cb*/, bool /*wait*/, bool /*advise_erase*/, bool& is_in_sec_cache) override { is_in_sec_cache = true; return nullptr; } bool SupportForceErase() const override { return false; } void Erase(const Slice& /*key*/) override {} // Wait for a collection of handles to become ready void WaitAll(std::vector /*handles*/) override {} std::string GetPrintableOptions() const override { return ""; } }; class TestStatistics : public StatisticsImpl { public: TestStatistics() : StatisticsImpl(nullptr) {} const char* Name() const override { return kClassName(); } static const char* kClassName() { return "Test"; } }; class TestFlushBlockPolicyFactory : public FlushBlockPolicyFactory { public: TestFlushBlockPolicyFactory() {} static const char* kClassName() { return "TestFlushBlockPolicyFactory"; } const char* Name() const override { return kClassName(); } FlushBlockPolicy* NewFlushBlockPolicy( const BlockBasedTableOptions& /*table_options*/, const BlockBuilder& /*data_block_builder*/) const override { return nullptr; } }; class MockSliceTransform : public SliceTransform { public: const char* Name() const override { return kClassName(); } static const char* kClassName() { return "Mock"; } Slice Transform(const Slice& /*key*/) const override { return Slice(); } bool InDomain(const Slice& /*key*/) const override { return false; } bool InRange(const Slice& /*key*/) const override { return false; } }; class MockMemoryAllocator : public BaseMemoryAllocator { public: static const char* kClassName() { return "MockMemoryAllocator"; } const char* Name() const override { return kClassName(); } }; #ifndef ROCKSDB_LITE class MockEncryptionProvider : public EncryptionProvider { public: explicit MockEncryptionProvider(const std::string& id) : id_(id) {} static const char* kClassName() { return "Mock"; } const char* Name() const override { return kClassName(); } size_t GetPrefixLength() const override { return 0; } Status CreateNewPrefix(const std::string& /*fname*/, char* /*prefix*/, size_t /*prefixLength*/) const override { return Status::NotSupported(); } Status AddCipher(const std::string& /*descriptor*/, const char* /*cipher*/, size_t /*len*/, bool /*for_write*/) override { return Status::NotSupported(); } Status CreateCipherStream( const std::string& /*fname*/, const EnvOptions& /*options*/, Slice& /*prefix*/, std::unique_ptr* /*result*/) override { return Status::NotSupported(); } Status ValidateOptions(const DBOptions& db_opts, const ColumnFamilyOptions& cf_opts) const override { if (EndsWith(id_, "://test")) { return EncryptionProvider::ValidateOptions(db_opts, cf_opts); } else { return Status::InvalidArgument("MockProvider not initialized"); } } private: std::string id_; }; class MockCipher : public BlockCipher { public: const char* Name() const override { return "Mock"; } size_t BlockSize() override { return 0; } Status Encrypt(char* /*data*/) override { return Status::NotSupported(); } Status Decrypt(char* data) override { return Encrypt(data); } }; #endif // ROCKSDB_LITE class DummyFileSystem : public FileSystemWrapper { public: explicit DummyFileSystem(const std::shared_ptr& t) : FileSystemWrapper(t) {} static const char* kClassName() { return "DummyFileSystem"; } const char* Name() const override { return kClassName(); } }; #ifndef ROCKSDB_LITE #endif // ROCKSDB_LITE class MockTablePropertiesCollectorFactory : public TablePropertiesCollectorFactory { private: public: TablePropertiesCollector* CreateTablePropertiesCollector( TablePropertiesCollectorFactory::Context /*context*/) override { return nullptr; } static const char* kClassName() { return "Mock"; } const char* Name() const override { return kClassName(); } }; class MockSstPartitionerFactory : public SstPartitionerFactory { public: static const char* kClassName() { return "Mock"; } const char* Name() const override { return kClassName(); } std::unique_ptr CreatePartitioner( const SstPartitioner::Context& /* context */) const override { return nullptr; } }; class MockFileChecksumGenFactory : public FileChecksumGenFactory { public: static const char* kClassName() { return "Mock"; } const char* Name() const override { return kClassName(); } std::unique_ptr CreateFileChecksumGenerator( const FileChecksumGenContext& /*context*/) override { return nullptr; } }; class MockFilterPolicy : public FilterPolicy { public: static const char* kClassName() { return "MockFilterPolicy"; } const char* Name() const override { return kClassName(); } const char* CompatibilityName() const override { return Name(); } FilterBitsBuilder* GetBuilderWithContext( const FilterBuildingContext&) const override { return nullptr; } FilterBitsReader* GetFilterBitsReader( const Slice& /*contents*/) const override { return nullptr; } }; #ifndef ROCKSDB_LITE static int RegisterLocalObjects(ObjectLibrary& library, const std::string& /*arg*/) { size_t num_types; library.AddFactory( mock::MockTableFactory::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new mock::MockTableFactory()); return guard->get(); }); library.AddFactory( OnFileDeletionListener::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new OnFileDeletionListener()); return guard->get(); }); library.AddFactory( FlushCounterListener::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new FlushCounterListener()); return guard->get(); }); // Load any locally defined objects here library.AddFactory( MockSliceTransform::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockSliceTransform()); return guard->get(); }); library.AddFactory( TestStatistics::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new TestStatistics()); return guard->get(); }); library.AddFactory( ObjectLibrary::PatternEntry(MockEncryptionProvider::kClassName(), true) .AddSuffix("://test"), [](const std::string& uri, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockEncryptionProvider(uri)); return guard->get(); }); library.AddFactory( "Mock", [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockCipher()); return guard->get(); }); library.AddFactory( MockMemoryAllocator::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockMemoryAllocator()); return guard->get(); }); library.AddFactory( TestFlushBlockPolicyFactory::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new TestFlushBlockPolicyFactory()); return guard->get(); }); library.AddFactory( TestSecondaryCache::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new TestSecondaryCache()); return guard->get(); }); library.AddFactory( DummyFileSystem::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new DummyFileSystem(nullptr)); return guard->get(); }); library.AddFactory( MockSstPartitionerFactory::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockSstPartitionerFactory()); return guard->get(); }); library.AddFactory( MockFileChecksumGenFactory::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockFileChecksumGenFactory()); return guard->get(); }); library.AddFactory( MockTablePropertiesCollectorFactory::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockTablePropertiesCollectorFactory()); return guard->get(); }); library.AddFactory( MockFilterPolicy::kClassName(), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MockFilterPolicy()); return guard->get(); }); return static_cast(library.GetFactoryCount(&num_types)); } #endif // !ROCKSDB_LITE } // namespace class LoadCustomizableTest : public testing::Test { public: LoadCustomizableTest() { config_options_.ignore_unsupported_options = false; config_options_.invoke_prepare_options = false; } bool RegisterTests(const std::string& arg) { #ifndef ROCKSDB_LITE config_options_.registry->AddLibrary("custom-tests", test::RegisterTestObjects, arg); config_options_.registry->AddLibrary("local-tests", RegisterLocalObjects, arg); return true; #else (void)arg; return false; #endif // !ROCKSDB_LITE } template Status TestCreateStatic(const std::string& name, U** result, bool delete_result = false) { Status s = T::CreateFromString(config_options_, name, result); if (s.ok()) { EXPECT_NE(*result, nullptr); EXPECT_TRUE(*result != nullptr && (*result)->IsInstanceOf(name)); } if (delete_result) { delete *result; *result = nullptr; } return s; } template std::shared_ptr ExpectCreateShared(const std::string& name, std::shared_ptr* object) { EXPECT_OK(T::CreateFromString(config_options_, name, object)); EXPECT_NE(object->get(), nullptr); EXPECT_TRUE(object->get()->IsInstanceOf(name)); return *object; } template std::shared_ptr ExpectCreateShared(const std::string& name) { std::shared_ptr result; return ExpectCreateShared(name, &result); } template Status TestExpectedBuiltins( const std::string& mock, const std::unordered_set& expected, std::shared_ptr* object, std::vector* failed, const std::function(const std::string&)>& alt = nullptr) { std::unordered_set factories = expected; Status s = T::CreateFromString(config_options_, mock, object); EXPECT_NOK(s); #ifndef ROCKSDB_LITE std::vector builtins; ObjectLibrary::Default()->GetFactoryNames(T::Type(), &builtins); factories.insert(builtins.begin(), builtins.end()); #endif // ROCKSDB_LITE Status result; int created = 0; for (const auto& name : factories) { created++; s = T::CreateFromString(config_options_, name, object); if (!s.ok() && alt != nullptr) { for (const auto& alt_name : alt(name)) { s = T::CreateFromString(config_options_, alt_name, object); if (s.ok()) { break; } } } if (!s.ok()) { result = s; failed->push_back(name); } else { EXPECT_NE(object->get(), nullptr); EXPECT_TRUE(object->get()->IsInstanceOf(name)); } } #ifndef ROCKSDB_LITE std::vector plugins; ObjectRegistry::Default()->GetFactoryNames(T::Type(), &plugins); if (plugins.size() > builtins.size()) { for (const auto& name : plugins) { if (factories.find(name) == factories.end()) { created++; s = T::CreateFromString(config_options_, name, object); if (!s.ok() && alt != nullptr) { for (const auto& alt_name : alt(name)) { s = T::CreateFromString(config_options_, alt_name, object); if (s.ok()) { break; } } } if (!s.ok()) { failed->push_back(name); if (result.ok()) { result = s; } printf("%s: Failed creating plugin[%s]: %s\n", T::Type(), name.c_str(), s.ToString().c_str()); } else if (object->get() == nullptr || !object->get()->IsInstanceOf(name)) { failed->push_back(name); printf("%s: Invalid plugin[%s]\n", T::Type(), name.c_str()); } } } } printf("%s: Created %d (expected+builtins+plugins %d+%d+%d) %d Failed\n", T::Type(), created, (int)expected.size(), (int)(factories.size() - expected.size()), (int)(plugins.size() - builtins.size()), (int)failed->size()); #else printf("%s: Created %d (expected %d) %d Failed\n", T::Type(), created, (int)expected.size(), (int)failed->size()); #endif // ROCKSDB_LITE return result; } template Status TestSharedBuiltins(const std::string& mock, const std::string& expected, std::vector* failed = nullptr) { std::unordered_set values; if (!expected.empty()) { values.insert(expected); } std::shared_ptr object; if (failed != nullptr) { return TestExpectedBuiltins(mock, values, &object, failed); } else { std::vector failures; Status s = TestExpectedBuiltins(mock, values, &object, &failures); EXPECT_EQ(0U, failures.size()); return s; } } template Status TestStaticBuiltins(const std::string& mock, U** object, const std::unordered_set& expected, std::vector* failed, bool delete_objects = false) { std::unordered_set factories = expected; Status s = TestCreateStatic(mock, object, delete_objects); EXPECT_NOK(s); #ifndef ROCKSDB_LITE std::vector builtins; ObjectLibrary::Default()->GetFactoryNames(T::Type(), &builtins); factories.insert(builtins.begin(), builtins.end()); #endif // ROCKSDB_LITE int created = 0; Status result; for (const auto& name : factories) { created++; s = TestCreateStatic(name, object, delete_objects); if (!s.ok()) { result = s; failed->push_back(name); } } #ifndef ROCKSDB_LITE std::vector plugins; ObjectRegistry::Default()->GetFactoryNames(T::Type(), &plugins); if (plugins.size() > builtins.size()) { for (const auto& name : plugins) { if (factories.find(name) == factories.end()) { created++; s = T::CreateFromString(config_options_, name, object); if (!s.ok() || *object == nullptr || !((*object)->IsInstanceOf(name))) { failed->push_back(name); if (result.ok() && !s.ok()) { result = s; } printf("%s: Failed creating plugin[%s]: %s\n", T::Type(), name.c_str(), s.ToString().c_str()); } if (delete_objects) { delete *object; *object = nullptr; } } } } printf("%s: Created %d (expected+builtins+plugins %d+%d+%d) %d Failed\n", T::Type(), created, (int)expected.size(), (int)(factories.size() - expected.size()), (int)(plugins.size() - builtins.size()), (int)failed->size()); #else printf("%s: Created %d (expected %d) %d Failed\n", T::Type(), created, (int)expected.size(), (int)failed->size()); #endif // ROCKSDB_LITE return result; } protected: DBOptions db_opts_; ColumnFamilyOptions cf_opts_; ConfigOptions config_options_; }; TEST_F(LoadCustomizableTest, LoadTableFactoryTest) { ASSERT_OK( TestSharedBuiltins(mock::MockTableFactory::kClassName(), TableFactory::kBlockBasedTableName())); #ifndef ROCKSDB_LITE std::string opts_str = "table_factory="; ASSERT_OK(GetColumnFamilyOptionsFromString( config_options_, cf_opts_, opts_str + TableFactory::kBlockBasedTableName(), &cf_opts_)); ASSERT_NE(cf_opts_.table_factory.get(), nullptr); ASSERT_STREQ(cf_opts_.table_factory->Name(), TableFactory::kBlockBasedTableName()); #endif // ROCKSDB_LITE if (RegisterTests("Test")) { ExpectCreateShared(mock::MockTableFactory::kClassName()); #ifndef ROCKSDB_LITE ASSERT_OK(GetColumnFamilyOptionsFromString( config_options_, cf_opts_, opts_str + mock::MockTableFactory::kClassName(), &cf_opts_)); ASSERT_NE(cf_opts_.table_factory.get(), nullptr); ASSERT_STREQ(cf_opts_.table_factory->Name(), mock::MockTableFactory::kClassName()); #endif // ROCKSDB_LITE } } TEST_F(LoadCustomizableTest, LoadFileSystemTest) { ASSERT_OK(TestSharedBuiltins(DummyFileSystem::kClassName(), FileSystem::kDefaultName())); if (RegisterTests("Test")) { auto fs = ExpectCreateShared(DummyFileSystem::kClassName()); ASSERT_FALSE(fs->IsInstanceOf(FileSystem::kDefaultName())); } } TEST_F(LoadCustomizableTest, LoadSecondaryCacheTest) { ASSERT_OK( TestSharedBuiltins(TestSecondaryCache::kClassName(), "")); if (RegisterTests("Test")) { ExpectCreateShared(TestSecondaryCache::kClassName()); } } #ifndef ROCKSDB_LITE TEST_F(LoadCustomizableTest, LoadSstPartitionerFactoryTest) { ASSERT_OK(TestSharedBuiltins( "Mock", SstPartitionerFixedPrefixFactory::kClassName())); if (RegisterTests("Test")) { ExpectCreateShared("Mock"); } } #endif // ROCKSDB_LITE TEST_F(LoadCustomizableTest, LoadChecksumGenFactoryTest) { ASSERT_OK(TestSharedBuiltins("Mock", "")); if (RegisterTests("Test")) { ExpectCreateShared("Mock"); } } TEST_F(LoadCustomizableTest, LoadTablePropertiesCollectorFactoryTest) { ASSERT_OK(TestSharedBuiltins( MockTablePropertiesCollectorFactory::kClassName(), "")); if (RegisterTests("Test")) { ExpectCreateShared( MockTablePropertiesCollectorFactory::kClassName()); } } TEST_F(LoadCustomizableTest, LoadComparatorTest) { const Comparator* bytewise = BytewiseComparator(); const Comparator* reverse = ReverseBytewiseComparator(); const Comparator* result = nullptr; std::unordered_set expected = {bytewise->Name(), reverse->Name()}; std::vector failures; ASSERT_OK(TestStaticBuiltins( test::SimpleSuffixReverseComparator::kClassName(), &result, expected, &failures)); if (RegisterTests("Test")) { ASSERT_OK(TestCreateStatic( test::SimpleSuffixReverseComparator::kClassName(), &result)); } } TEST_F(LoadCustomizableTest, LoadSliceTransformFactoryTest) { std::shared_ptr result; std::vector failures; std::unordered_set expected = {"rocksdb.Noop", "fixed", "rocksdb.FixedPrefix", "capped", "rocksdb.CappedPrefix"}; ASSERT_OK(TestExpectedBuiltins( "Mock", expected, &result, &failures, [](const std::string& name) { std::vector names = {name + ":22", name + ".22"}; return names; })); ASSERT_OK(SliceTransform::CreateFromString( config_options_, "rocksdb.FixedPrefix.22", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf("fixed")); ASSERT_OK(SliceTransform::CreateFromString( config_options_, "rocksdb.CappedPrefix.22", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf("capped")); if (RegisterTests("Test")) { ExpectCreateShared("Mock", &result); } } TEST_F(LoadCustomizableTest, LoadStatisticsTest) { ASSERT_OK(TestSharedBuiltins(TestStatistics::kClassName(), "BasicStatistics")); // Empty will create a default BasicStatistics ASSERT_OK( Statistics::CreateFromString(config_options_, "", &db_opts_.statistics)); ASSERT_NE(db_opts_.statistics, nullptr); ASSERT_STREQ(db_opts_.statistics->Name(), "BasicStatistics"); #ifndef ROCKSDB_LITE ASSERT_NOK(GetDBOptionsFromString(config_options_, db_opts_, "statistics=Test", &db_opts_)); ASSERT_OK(GetDBOptionsFromString(config_options_, db_opts_, "statistics=BasicStatistics", &db_opts_)); ASSERT_NE(db_opts_.statistics, nullptr); ASSERT_STREQ(db_opts_.statistics->Name(), "BasicStatistics"); if (RegisterTests("test")) { auto stats = ExpectCreateShared(TestStatistics::kClassName()); ASSERT_OK(GetDBOptionsFromString(config_options_, db_opts_, "statistics=Test", &db_opts_)); ASSERT_NE(db_opts_.statistics, nullptr); ASSERT_STREQ(db_opts_.statistics->Name(), TestStatistics::kClassName()); ASSERT_OK(GetDBOptionsFromString( config_options_, db_opts_, "statistics={id=Test;inner=BasicStatistics}", &db_opts_)); ASSERT_NE(db_opts_.statistics, nullptr); ASSERT_STREQ(db_opts_.statistics->Name(), TestStatistics::kClassName()); auto* inner = db_opts_.statistics->GetOptions>( "StatisticsOptions"); ASSERT_NE(inner, nullptr); ASSERT_NE(inner->get(), nullptr); ASSERT_STREQ(inner->get()->Name(), "BasicStatistics"); ASSERT_OK(Statistics::CreateFromString( config_options_, "id=BasicStatistics;inner=Test", &stats)); ASSERT_NE(stats, nullptr); ASSERT_STREQ(stats->Name(), "BasicStatistics"); inner = stats->GetOptions>("StatisticsOptions"); ASSERT_NE(inner, nullptr); ASSERT_NE(inner->get(), nullptr); ASSERT_STREQ(inner->get()->Name(), TestStatistics::kClassName()); } #endif } TEST_F(LoadCustomizableTest, LoadMemTableRepFactoryTest) { std::unordered_set expected = { SkipListFactory::kClassName(), SkipListFactory::kNickName(), }; std::vector failures; std::shared_ptr factory; Status s = TestExpectedBuiltins( "SpecialSkipListFactory", expected, &factory, &failures); // There is a "cuckoo" factory registered that we expect to fail. Ignore the // error if this is the one if (s.ok() || failures.size() > 1 || failures[0] != "cuckoo") { ASSERT_OK(s); } if (RegisterTests("Test")) { ExpectCreateShared("SpecialSkipListFactory"); } } TEST_F(LoadCustomizableTest, LoadMergeOperatorTest) { std::shared_ptr result; std::vector failed; std::unordered_set expected = { "put", "put_v1", "PutOperator", "uint64add", "UInt64AddOperator", "max", "MaxOperator", }; #ifndef ROCKSDB_LITE expected.insert({ StringAppendOperator::kClassName(), StringAppendOperator::kNickName(), StringAppendTESTOperator::kClassName(), StringAppendTESTOperator::kNickName(), SortList::kClassName(), SortList::kNickName(), BytesXOROperator::kClassName(), BytesXOROperator::kNickName(), }); #endif // ROCKSDB_LITE ASSERT_OK(TestExpectedBuiltins("Changling", expected, &result, &failed)); if (RegisterTests("Test")) { ExpectCreateShared("Changling"); } } TEST_F(LoadCustomizableTest, LoadCompactionFilterFactoryTest) { ASSERT_OK(TestSharedBuiltins("Changling", "")); if (RegisterTests("Test")) { ExpectCreateShared("Changling"); } } TEST_F(LoadCustomizableTest, LoadCompactionFilterTest) { const CompactionFilter* result = nullptr; std::vector failures; ASSERT_OK(TestStaticBuiltins("Changling", &result, {}, &failures, true)); if (RegisterTests("Test")) { ASSERT_OK(TestCreateStatic("Changling", &result, true)); } } #ifndef ROCKSDB_LITE TEST_F(LoadCustomizableTest, LoadEventListenerTest) { ASSERT_OK(TestSharedBuiltins( OnFileDeletionListener::kClassName(), "")); if (RegisterTests("Test")) { ExpectCreateShared(OnFileDeletionListener::kClassName()); ExpectCreateShared(FlushCounterListener::kClassName()); } } TEST_F(LoadCustomizableTest, LoadEncryptionProviderTest) { std::vector failures; std::shared_ptr result; ASSERT_OK( TestExpectedBuiltins("Mock", {}, &result, &failures)); if (!failures.empty()) { ASSERT_EQ(failures[0], "1://test"); ASSERT_EQ(failures.size(), 1U); } result = ExpectCreateShared("CTR"); ASSERT_NOK(result->ValidateOptions(db_opts_, cf_opts_)); ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, "CTR://test", &result)); ASSERT_NE(result, nullptr); ASSERT_STREQ(result->Name(), "CTR"); ASSERT_OK(result->ValidateOptions(db_opts_, cf_opts_)); if (RegisterTests("Test")) { ExpectCreateShared("Mock"); ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, "Mock://test", &result)); ASSERT_NE(result, nullptr); ASSERT_STREQ(result->Name(), "Mock"); ASSERT_OK(result->ValidateOptions(db_opts_, cf_opts_)); } } TEST_F(LoadCustomizableTest, LoadEncryptionCipherTest) { ASSERT_OK(TestSharedBuiltins("Mock", "ROT13")); if (RegisterTests("Test")) { ExpectCreateShared("Mock"); } } #endif // !ROCKSDB_LITE TEST_F(LoadCustomizableTest, LoadSystemClockTest) { ASSERT_OK(TestSharedBuiltins(MockSystemClock::kClassName(), SystemClock::kDefaultName())); if (RegisterTests("Test")) { auto result = ExpectCreateShared(MockSystemClock::kClassName()); ASSERT_FALSE(result->IsInstanceOf(SystemClock::kDefaultName())); } } TEST_F(LoadCustomizableTest, LoadMemoryAllocatorTest) { std::vector failures; Status s = TestSharedBuiltins( MockMemoryAllocator::kClassName(), DefaultMemoryAllocator::kClassName(), &failures); if (failures.empty()) { ASSERT_OK(s); } else { ASSERT_NOK(s); for (const auto& failure : failures) { if (failure == JemallocNodumpAllocator::kClassName()) { ASSERT_FALSE(JemallocNodumpAllocator::IsSupported()); } else if (failure == MemkindKmemAllocator::kClassName()) { ASSERT_FALSE(MemkindKmemAllocator::IsSupported()); } else { printf("BYPASSED: %s -- %s\n", failure.c_str(), s.ToString().c_str()); } } } if (RegisterTests("Test")) { ExpectCreateShared(MockMemoryAllocator::kClassName()); } } TEST_F(LoadCustomizableTest, LoadFilterPolicyTest) { const std::string kAutoBloom = BloomFilterPolicy::kClassName(); const std::string kAutoRibbon = RibbonFilterPolicy::kClassName(); std::shared_ptr result; std::vector failures; std::unordered_set expected = { ReadOnlyBuiltinFilterPolicy::kClassName(), }; #ifndef ROCKSDB_LITE expected.insert({ kAutoBloom, BloomFilterPolicy::kNickName(), kAutoRibbon, RibbonFilterPolicy::kNickName(), }); #endif // ROCKSDB_LITE ASSERT_OK(TestExpectedBuiltins( "Mock", expected, &result, &failures, [](const std::string& name) { std::vector names = {name + ":1.234"}; return names; })); #ifndef ROCKSDB_LITE ASSERT_OK(FilterPolicy::CreateFromString( config_options_, kAutoBloom + ":1.234:false", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf(kAutoBloom)); ASSERT_OK(FilterPolicy::CreateFromString( config_options_, kAutoBloom + ":1.234:false", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf(kAutoBloom)); ASSERT_OK(FilterPolicy::CreateFromString(config_options_, kAutoRibbon + ":1.234:-1", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf(kAutoRibbon)); ASSERT_OK(FilterPolicy::CreateFromString(config_options_, kAutoRibbon + ":1.234:56", &result)); ASSERT_NE(result.get(), nullptr); ASSERT_TRUE(result->IsInstanceOf(kAutoRibbon)); #endif // ROCKSDB_LITE if (RegisterTests("Test")) { ExpectCreateShared(MockFilterPolicy::kClassName(), &result); } std::shared_ptr table; #ifndef ROCKSDB_LITE std::string table_opts = "id=BlockBasedTable; filter_policy="; ASSERT_OK(TableFactory::CreateFromString(config_options_, table_opts + "nullptr", &table)); ASSERT_NE(table.get(), nullptr); auto bbto = table->GetOptions(); ASSERT_NE(bbto, nullptr); ASSERT_EQ(bbto->filter_policy.get(), nullptr); ASSERT_OK(TableFactory::CreateFromString( config_options_, table_opts + ReadOnlyBuiltinFilterPolicy::kClassName(), &table)); bbto = table->GetOptions(); ASSERT_NE(bbto, nullptr); ASSERT_NE(bbto->filter_policy.get(), nullptr); ASSERT_STREQ(bbto->filter_policy->Name(), ReadOnlyBuiltinFilterPolicy::kClassName()); ASSERT_OK(TableFactory::CreateFromString( config_options_, table_opts + MockFilterPolicy::kClassName(), &table)); bbto = table->GetOptions(); ASSERT_NE(bbto, nullptr); ASSERT_NE(bbto->filter_policy.get(), nullptr); ASSERT_TRUE( bbto->filter_policy->IsInstanceOf(MockFilterPolicy::kClassName())); #endif // ROCKSDB_LITE } TEST_F(LoadCustomizableTest, LoadFlushBlockPolicyFactoryTest) { std::shared_ptr result; std::shared_ptr table; std::vector failed; std::unordered_set expected = { FlushBlockBySizePolicyFactory::kClassName(), FlushBlockEveryKeyPolicyFactory::kClassName(), }; ASSERT_OK(TestExpectedBuiltins( TestFlushBlockPolicyFactory::kClassName(), expected, &result, &failed)); // An empty policy name creates a BySize policy ASSERT_OK( FlushBlockPolicyFactory::CreateFromString(config_options_, "", &result)); ASSERT_NE(result, nullptr); ASSERT_STREQ(result->Name(), FlushBlockBySizePolicyFactory::kClassName()); #ifndef ROCKSDB_LITE std::string table_opts = "id=BlockBasedTable; flush_block_policy_factory="; ASSERT_OK(TableFactory::CreateFromString( config_options_, table_opts + FlushBlockEveryKeyPolicyFactory::kClassName(), &table)); auto bbto = table->GetOptions(); ASSERT_NE(bbto, nullptr); ASSERT_NE(bbto->flush_block_policy_factory.get(), nullptr); ASSERT_STREQ(bbto->flush_block_policy_factory->Name(), FlushBlockEveryKeyPolicyFactory::kClassName()); if (RegisterTests("Test")) { ExpectCreateShared( TestFlushBlockPolicyFactory::kClassName()); ASSERT_OK(TableFactory::CreateFromString( config_options_, table_opts + TestFlushBlockPolicyFactory::kClassName(), &table)); bbto = table->GetOptions(); ASSERT_NE(bbto, nullptr); ASSERT_NE(bbto->flush_block_policy_factory.get(), nullptr); ASSERT_STREQ(bbto->flush_block_policy_factory->Name(), TestFlushBlockPolicyFactory::kClassName()); } #endif // ROCKSDB_LITE } } // namespace ROCKSDB_NAMESPACE int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); #ifdef GFLAGS ParseCommandLineFlags(&argc, &argv, true); #endif // GFLAGS return RUN_ALL_TESTS(); }