diff options
Diffstat (limited to 'src/rocksdb/options/customizable_test.cc')
-rw-r--r-- | src/rocksdb/options/customizable_test.cc | 2255 |
1 files changed, 2255 insertions, 0 deletions
diff --git a/src/rocksdb/options/customizable_test.cc b/src/rocksdb/options/customizable_test.cc new file mode 100644 index 000000000..9d3c86c62 --- /dev/null +++ b/src/rocksdb/options/customizable_test.cc @@ -0,0 +1,2255 @@ +// 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 <cctype> +#include <cinttypes> +#include <cstring> +#include <unordered_map> +#include <unordered_set> + +#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<TestCustomizable>* result); + static Status CreateFromString(const ConfigOptions& opts, + const std::string& value, + std::shared_ptr<TestCustomizable>* 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<std::string, OptionTypeInfo> 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<std::string, OptionTypeInfo> 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<TestCustomizable>* 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<TestCustomizable>( + ObjectLibrary::PatternEntry("A", true).AddSeparator("_"), + [](const std::string& name, std::unique_ptr<TestCustomizable>* guard, + std::string* /* msg */) { + guard->reset(new ACustomizable(name)); + A_count++; + return guard->get(); + }); + + library.AddFactory<TestCustomizable>( + "S", [](const std::string& name, + std::unique_ptr<TestCustomizable>* /* guard */, + std::string* /* msg */) { return new BCustomizable(name); }); + size_t num_types; + return static_cast<int>(library.GetFactoryCount(&num_types)); +} +#endif // ROCKSDB_LITE + +struct SimpleOptions { + static const char* kName() { return "simple"; } + bool b = true; + std::unique_ptr<TestCustomizable> cu; + std::shared_ptr<TestCustomizable> cs; + TestCustomizable* cp = nullptr; +}; + +static std::unordered_map<std::string, OptionTypeInfo> simple_option_info = { +#ifndef ROCKSDB_LITE + {"bool", + {offsetof(struct SimpleOptions, b), OptionType::kBoolean, + OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, + {"unique", + OptionTypeInfo::AsCustomUniquePtr<TestCustomizable>( + offsetof(struct SimpleOptions, cu), OptionVerificationType::kNormal, + OptionTypeFlags::kAllowNull)}, + {"shared", + OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>( + offsetof(struct SimpleOptions, cs), OptionVerificationType::kNormal, + OptionTypeFlags::kAllowNull)}, + {"pointer", + OptionTypeInfo::AsCustomRawPtr<TestCustomizable>( + 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<std::string, OptionTypeInfo>* map) { + RegisterOptions(&simple_, map); + } +}; + +#ifndef ROCKSDB_LITE +static void GetMapFromProperties( + const std::string& props, + std::unordered_map<std::string, std::string>* map) { + std::istringstream iss(props); + std::unordered_map<std::string, std::string> 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<TestCustomizable>* result) { + return LoadSharedObject<TestCustomizable>(config_options, value, LoadSharedB, + result); +} + +Status TestCustomizable::CreateFromString( + const ConfigOptions& config_options, const std::string& value, + std::unique_ptr<TestCustomizable>* result) { + return LoadUniqueObject<TestCustomizable>( + config_options, value, + [](const std::string& id, std::unique_ptr<TestCustomizable>* 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<TestCustomizable>( + 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<TestCustomizable>( + ObjectLibrary::PatternEntry("TEST", false).AddSeparator("_"), + [](const std::string& name, std::unique_ptr<TestCustomizable>* guard, + std::string* /* msg */) { + guard->reset(new TestCustomizable(name)); + return guard->get(); + }); + std::unique_ptr<Configurable> configurable(new SimpleConfigurable()); + SimpleOptions* simple = configurable->GetOptions<SimpleOptions>(); + 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<TestCustomizable> custom(new TestCustomizable("test")); + ASSERT_EQ(custom->ToString(config_options_), "test"); +} + +TEST_F(CustomizableTest, SimpleConfigureTest) { + std::unordered_map<std::string, std::string> opt_map = { + {"unique", "id=A;int=1;bool=true"}, + {"shared", "id=B;string=s"}, + }; + std::unique_ptr<Configurable> configurable(new SimpleConfigurable()); + ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); + SimpleOptions* simple = configurable->GetOptions<SimpleOptions>(); + 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<Configurable> 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<std::string, std::string> opt_map = { + {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, + {"shared.id", "B"}, {"shared.B.string", "s"}, + }; + std::unique_ptr<Configurable> configurable(new SimpleConfigurable()); + ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); + SimpleOptions* simple = configurable->GetOptions<SimpleOptions>(); + 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<std::string, std::string> props; + ASSERT_OK(configurable->GetOptionString(config_options_, &opt_str)); + GetMapFromProperties(opt_str, &props); + std::unique_ptr<Configurable> 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<std::string, std::string> opt_map = { + {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, + {"shared.id", "B"}, {"shared.B.string", "s"}, + }; + std::unique_ptr<Configurable> configurable(new SimpleConfigurable()); + ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); + SimpleOptions* simple = configurable->GetOptions<SimpleOptions>(); + ASSERT_NE(simple, nullptr); + ASSERT_NE(simple->cu, nullptr); + ASSERT_EQ(simple->cu->GetId(), "A"); +} + +TEST_F(CustomizableTest, AreEquivalentOptionsTest) { + std::unordered_map<std::string, std::string> 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<Configurable> c1(new SimpleConfigurable()); + std::unique_ptr<Configurable> 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<SimpleOptions>(); + 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<TestCustomizable> base, copy; + const auto& registry = config_options_.registry; + ASSERT_OK(registry->NewUniqueObject<TestCustomizable>("A", &base)); + ASSERT_OK(registry->NewUniqueObject<TestCustomizable>("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<Configurable> 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<Configurable> 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<ObjectRegistry> registry = ObjectRegistry::NewInstance(); + std::unique_ptr<Configurable> c1(new SimpleConfigurable()); + ConfigOptions ignore = config_options_; + + Status s; + ignore.registry->AddLibrary("failing")->AddFactory<TestCustomizable>( + "failing", + [](const std::string& /*uri*/, + std::unique_ptr<TestCustomizable>* /*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<Configurable> base(new SimpleConfigurable()); + ASSERT_OK(base->ConfigureFromString(config_options_, + "unique={id=A_1;int=1;bool=true}")); + SimpleOptions* simple = base->GetOptions<SimpleOptions>(); + 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<Configurable> 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<TestCustomizable> tc = std::make_shared<ACustomizable>("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<ACustomizable>(), tc.get()); + ASSERT_EQ(tc->CheckedCast<TestCustomizable>(), tc.get()); + ASSERT_EQ(tc->CheckedCast<BCustomizable>(), 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<BCustomizable>(), tc.get()); + ASSERT_EQ(tc->CheckedCast<TestCustomizable>(), tc.get()); + ASSERT_EQ(tc->CheckedCast<ACustomizable>(), nullptr); +} + +TEST_F(CustomizableTest, PrepareOptionsTest) { + static std::unordered_map<std::string, OptionTypeInfo> 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<TestCustomizable>( + "P", + [](const std::string& /*name*/, std::unique_ptr<TestCustomizable>* guard, + std::string* /* msg */) { + guard->reset(new PrepareCustomizable()); + return guard->get(); + }); + + std::unique_ptr<Configurable> 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<SimpleOptions>(); + 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<SimpleOptions>(); + 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<SimpleOptions>(); + 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<std::string, OptionTypeInfo> inner_option_info = { +#ifndef ROCKSDB_LITE + {"inner", + OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>( + 0, OptionVerificationType::kNormal, OptionTypeFlags::kStringNameOnly)} +#endif // ROCKSDB_LITE +}; + +struct InnerOptions { + static const char* kName() { return "InnerOptions"; } + std::shared_ptr<Customizable> inner; +}; + +class InnerCustomizable : public Customizable { + public: + explicit InnerCustomizable(const std::shared_ptr<Customizable>& 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<Customizable>& 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<Customizable>& 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<TestCustomizable> ac = + std::make_shared<TestCustomizable>("A"); + + ASSERT_TRUE(ac->IsInstanceOf("A")); + ASSERT_TRUE(ac->IsInstanceOf("TestCustomizable")); + ASSERT_EQ(ac->CheckedCast<TestCustomizable>(), ac.get()); + ASSERT_EQ(ac->CheckedCast<InnerCustomizable>(), nullptr); + ASSERT_EQ(ac->CheckedCast<WrappedCustomizable1>(), nullptr); + ASSERT_EQ(ac->CheckedCast<WrappedCustomizable2>(), nullptr); + std::shared_ptr<Customizable> wc1 = + std::make_shared<WrappedCustomizable1>(ac); + + ASSERT_TRUE(wc1->IsInstanceOf(WrappedCustomizable1::kClassName())); + ASSERT_EQ(wc1->CheckedCast<WrappedCustomizable1>(), wc1.get()); + ASSERT_EQ(wc1->CheckedCast<WrappedCustomizable2>(), nullptr); + ASSERT_EQ(wc1->CheckedCast<InnerCustomizable>(), wc1.get()); + ASSERT_EQ(wc1->CheckedCast<TestCustomizable>(), ac.get()); + + std::shared_ptr<Customizable> wc2 = + std::make_shared<WrappedCustomizable2>(wc1); + ASSERT_TRUE(wc2->IsInstanceOf(WrappedCustomizable2::kClassName())); + ASSERT_EQ(wc2->CheckedCast<WrappedCustomizable2>(), wc2.get()); + ASSERT_EQ(wc2->CheckedCast<WrappedCustomizable1>(), wc1.get()); + ASSERT_EQ(wc2->CheckedCast<InnerCustomizable>(), wc2.get()); + ASSERT_EQ(wc2->CheckedCast<TestCustomizable>(), ac.get()); +} + +TEST_F(CustomizableTest, CustomizableInnerTest) { + std::shared_ptr<Customizable> c = + std::make_shared<InnerCustomizable>(std::make_shared<ACustomizable>("a")); + std::shared_ptr<Customizable> wc1 = std::make_shared<WrappedCustomizable1>(c); + std::shared_ptr<Customizable> wc2 = std::make_shared<WrappedCustomizable2>(c); + auto inner = c->GetOptions<InnerOptions>(); + ASSERT_NE(inner, nullptr); + + auto aopts = c->GetOptions<AOptions>(); + ASSERT_NE(aopts, nullptr); + ASSERT_EQ(aopts, wc1->GetOptions<AOptions>()); + ASSERT_EQ(aopts, wc2->GetOptions<AOptions>()); + auto w1opts = wc1->GetOptions<WrappedOptions1>(); + ASSERT_NE(w1opts, nullptr); + ASSERT_EQ(c->GetOptions<WrappedOptions1>(), nullptr); + ASSERT_EQ(wc2->GetOptions<WrappedOptions1>(), nullptr); + + auto w2opts = wc2->GetOptions<WrappedOptions2>(); + ASSERT_NE(w2opts, nullptr); + ASSERT_EQ(c->GetOptions<WrappedOptions2>(), nullptr); + ASSERT_EQ(wc1->GetOptions<WrappedOptions2>(), 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<Configurable> c( + new InnerCustomizable(std::make_shared<ACustomizable>("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<Configurable> base(new SimpleConfigurable()); + A_count = 0; + ASSERT_OK(base->ConfigureFromString(config_options_, + "unique={id=A_1;int=1;bool=true}")); + SimpleOptions* simple = base->GetOptions<SimpleOptions>(); + 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<Configurable> base(new SimpleConfigurable()); + SimpleOptions* simple = base->GetOptions<SimpleOptions>(); + 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<Configurable> base(new SimpleConfigurable()); + + SimpleOptions* simple = base->GetOptions<SimpleOptions>(); + 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<Configurable> base(new SimpleConfigurable()); + ASSERT_OK(base->ConfigureFromString(config_options_, "pointer={id=}")); + SimpleOptions* simple = base->GetOptions<SimpleOptions>(); + 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<std::string, OptionTypeInfo> vector_option_info = { + {"vector", + OptionTypeInfo::Vector<std::shared_ptr<TestCustomizable>>( + 0, OptionVerificationType::kNormal, + + OptionTypeFlags::kNone, + + OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>( + 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone))}, +}; +class VectorConfigurable : public SimpleConfigurable { + public: + VectorConfigurable() { RegisterOptions("vector", &cv, &vector_option_info); } + std::vector<std::shared_ptr<TestCustomizable>> cv; +}; +} // namespace + +TEST_F(CustomizableTest, VectorConfigTest) { + VectorConfigurable orig, copy; + std::shared_ptr<TestCustomizable> 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<SimpleOptions>(); + auto copts = copy.GetOptions<SimpleOptions>(); + sopts->cu.reset(new ACustomizable("")); + orig.cv.push_back(std::make_shared<ACustomizable>("")); + orig.cv.push_back(std::make_shared<ACustomizable>("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<TestCustomizable> shared; + std::unique_ptr<TestCustomizable> unique; + TestCustomizable* pointer = nullptr; + ignore.ignore_unsupported_options = false; + ASSERT_NOK( + LoadSharedObject<TestCustomizable>(ignore, "Unknown", nullptr, &shared)); + ASSERT_NOK( + LoadUniqueObject<TestCustomizable>(ignore, "Unknown", nullptr, &unique)); + ASSERT_NOK( + LoadStaticObject<TestCustomizable>(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<TestCustomizable>(ignore, "Unknown", nullptr, &shared)); + ASSERT_OK( + LoadUniqueObject<TestCustomizable>(ignore, "Unknown", nullptr, &unique)); + ASSERT_OK( + LoadStaticObject<TestCustomizable>(ignore, "Unknown", nullptr, &pointer)); + ASSERT_EQ(shared.get(), nullptr); + ASSERT_EQ(unique.get(), nullptr); + ASSERT_EQ(pointer, nullptr); + ASSERT_OK(LoadSharedObject<TestCustomizable>(ignore, "id=Unknown", nullptr, + &shared)); + ASSERT_OK(LoadUniqueObject<TestCustomizable>(ignore, "id=Unknown", nullptr, + &unique)); + ASSERT_OK(LoadStaticObject<TestCustomizable>(ignore, "id=Unknown", nullptr, + &pointer)); + ASSERT_EQ(shared.get(), nullptr); + ASSERT_EQ(unique.get(), nullptr); + ASSERT_EQ(pointer, nullptr); + ASSERT_OK(LoadSharedObject<TestCustomizable>(ignore, "id=Unknown;option=bad", + nullptr, &shared)); + ASSERT_OK(LoadUniqueObject<TestCustomizable>(ignore, "id=Unknown;option=bad", + nullptr, &unique)); + ASSERT_OK(LoadStaticObject<TestCustomizable>(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<TestCustomizable> shared; + std::unique_ptr<TestCustomizable> 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<TestCustomizable> unique; + config_options_.registry->AddLibrary("URL")->AddFactory<TestCustomizable>( + ObjectLibrary::PatternEntry("Z", false).AddSeparator(""), + [](const std::string& name, std::unique_ptr<TestCustomizable>* 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<std::string, OptionTypeInfo> mutable_option_info = { + {"mutable", + OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>( + 0, OptionVerificationType::kNormal, OptionTypeFlags::kMutable)}}; + static std::unordered_map<std::string, OptionTypeInfo> immutable_option_info = + {{"immutable", + OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>( + 0, OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}}; + + class MutableCustomizable : public Customizable { + private: + std::shared_ptr<TestCustomizable> mutable_; + std::shared_ptr<TestCustomizable> 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<std::shared_ptr<TestCustomizable>>("mutable"); + auto* im = mc.GetOptions<std::shared_ptr<TestCustomizable>>("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<AOptions>("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<AOptions>("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<AOptions>("A"); + ASSERT_EQ(mm_a->b, true); + ASSERT_OK(mc.ConfigureOption(options, "mutable", "{int=22;bool=false}")); + mm_a = mm->get()->GetOptions<AOptions>("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<TestCustomizable> object1, object2; + ASSERT_OK(LoadManagedObject<TestCustomizable>( + config_options_, "id=A_1;int=1;bool=true", &object1)); + ASSERT_NE(object1, nullptr); + ASSERT_OK( + LoadManagedObject<TestCustomizable>(config_options_, "A_1", &object2)); + ASSERT_EQ(object1, object2); + auto* opts = object2->GetOptions<AOptions>("A"); + ASSERT_NE(opts, nullptr); + ASSERT_EQ(opts->i, 1); + ASSERT_EQ(opts->b, true); + ASSERT_OK( + LoadManagedObject<TestCustomizable>(config_options_, "A_2", &object2)); + ASSERT_NE(object1, object2); + object1.reset(); + ASSERT_OK(LoadManagedObject<TestCustomizable>( + config_options_, "id=A_1;int=2;bool=false", &object1)); + opts = object1->GetOptions<AOptions>("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<ManagedCustomizable>* result) { + return LoadManagedObject<ManagedCustomizable>(opts, value, result); + } + + private: + std::string id_; + }; + + config_options_.registry->AddLibrary("Managed") + ->AddFactory<ManagedCustomizable>( + ObjectLibrary::PatternEntry::AsIndividualId( + ManagedCustomizable::kClassName()), + [](const std::string& /*name*/, + std::unique_ptr<ManagedCustomizable>* guard, + std::string* /* msg */) { + guard->reset(new ManagedCustomizable()); + return guard->get(); + }); + + std::shared_ptr<ManagedCustomizable> 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<std::shared_ptr<ManagedCustomizable>> 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<ManagedCustomizable>(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<ManagedCustomizable>(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<SecondaryCacheResultHandle> 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<SecondaryCacheResultHandle*> /*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<BlockAccessCipherStream>* /*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<FileSystem>& 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<SstPartitioner> 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<FileChecksumGenerator> 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<TableFactory>( + mock::MockTableFactory::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<TableFactory>* guard, + std::string* /* errmsg */) { + guard->reset(new mock::MockTableFactory()); + return guard->get(); + }); + library.AddFactory<EventListener>( + OnFileDeletionListener::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<EventListener>* guard, + std::string* /* errmsg */) { + guard->reset(new OnFileDeletionListener()); + return guard->get(); + }); + library.AddFactory<EventListener>( + FlushCounterListener::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<EventListener>* guard, + std::string* /* errmsg */) { + guard->reset(new FlushCounterListener()); + return guard->get(); + }); + // Load any locally defined objects here + library.AddFactory<const SliceTransform>( + MockSliceTransform::kClassName(), + [](const std::string& /*uri*/, + std::unique_ptr<const SliceTransform>* guard, + std::string* /* errmsg */) { + guard->reset(new MockSliceTransform()); + return guard->get(); + }); + library.AddFactory<Statistics>( + TestStatistics::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<Statistics>* guard, + std::string* /* errmsg */) { + guard->reset(new TestStatistics()); + return guard->get(); + }); + + library.AddFactory<EncryptionProvider>( + ObjectLibrary::PatternEntry(MockEncryptionProvider::kClassName(), true) + .AddSuffix("://test"), + [](const std::string& uri, std::unique_ptr<EncryptionProvider>* guard, + std::string* /* errmsg */) { + guard->reset(new MockEncryptionProvider(uri)); + return guard->get(); + }); + library.AddFactory<BlockCipher>( + "Mock", + [](const std::string& /*uri*/, std::unique_ptr<BlockCipher>* guard, + std::string* /* errmsg */) { + guard->reset(new MockCipher()); + return guard->get(); + }); + library.AddFactory<MemoryAllocator>( + MockMemoryAllocator::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<MemoryAllocator>* guard, + std::string* /* errmsg */) { + guard->reset(new MockMemoryAllocator()); + return guard->get(); + }); + library.AddFactory<FlushBlockPolicyFactory>( + TestFlushBlockPolicyFactory::kClassName(), + [](const std::string& /*uri*/, + std::unique_ptr<FlushBlockPolicyFactory>* guard, + std::string* /* errmsg */) { + guard->reset(new TestFlushBlockPolicyFactory()); + return guard->get(); + }); + + library.AddFactory<SecondaryCache>( + TestSecondaryCache::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<SecondaryCache>* guard, + std::string* /* errmsg */) { + guard->reset(new TestSecondaryCache()); + return guard->get(); + }); + + library.AddFactory<FileSystem>( + DummyFileSystem::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<FileSystem>* guard, + std::string* /* errmsg */) { + guard->reset(new DummyFileSystem(nullptr)); + return guard->get(); + }); + + library.AddFactory<SstPartitionerFactory>( + MockSstPartitionerFactory::kClassName(), + [](const std::string& /*uri*/, + std::unique_ptr<SstPartitionerFactory>* guard, + std::string* /* errmsg */) { + guard->reset(new MockSstPartitionerFactory()); + return guard->get(); + }); + + library.AddFactory<FileChecksumGenFactory>( + MockFileChecksumGenFactory::kClassName(), + [](const std::string& /*uri*/, + std::unique_ptr<FileChecksumGenFactory>* guard, + std::string* /* errmsg */) { + guard->reset(new MockFileChecksumGenFactory()); + return guard->get(); + }); + + library.AddFactory<TablePropertiesCollectorFactory>( + MockTablePropertiesCollectorFactory::kClassName(), + [](const std::string& /*uri*/, + std::unique_ptr<TablePropertiesCollectorFactory>* guard, + std::string* /* errmsg */) { + guard->reset(new MockTablePropertiesCollectorFactory()); + return guard->get(); + }); + + library.AddFactory<const FilterPolicy>( + MockFilterPolicy::kClassName(), + [](const std::string& /*uri*/, std::unique_ptr<const FilterPolicy>* guard, + std::string* /* errmsg */) { + guard->reset(new MockFilterPolicy()); + return guard->get(); + }); + + return static_cast<int>(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 <typename T, typename U> + 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 <typename T, typename U> + std::shared_ptr<U> ExpectCreateShared(const std::string& name, + std::shared_ptr<U>* object) { + EXPECT_OK(T::CreateFromString(config_options_, name, object)); + EXPECT_NE(object->get(), nullptr); + EXPECT_TRUE(object->get()->IsInstanceOf(name)); + return *object; + } + + template <typename T> + std::shared_ptr<T> ExpectCreateShared(const std::string& name) { + std::shared_ptr<T> result; + return ExpectCreateShared<T>(name, &result); + } + + template <typename T, typename U> + Status TestExpectedBuiltins( + const std::string& mock, const std::unordered_set<std::string>& expected, + std::shared_ptr<U>* object, std::vector<std::string>* failed, + const std::function<std::vector<std::string>(const std::string&)>& alt = + nullptr) { + std::unordered_set<std::string> factories = expected; + Status s = T::CreateFromString(config_options_, mock, object); + EXPECT_NOK(s); +#ifndef ROCKSDB_LITE + std::vector<std::string> 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<std::string> 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 <typename T> + Status TestSharedBuiltins(const std::string& mock, + const std::string& expected, + std::vector<std::string>* failed = nullptr) { + std::unordered_set<std::string> values; + if (!expected.empty()) { + values.insert(expected); + } + std::shared_ptr<T> object; + if (failed != nullptr) { + return TestExpectedBuiltins<T>(mock, values, &object, failed); + } else { + std::vector<std::string> failures; + Status s = TestExpectedBuiltins<T>(mock, values, &object, &failures); + EXPECT_EQ(0U, failures.size()); + return s; + } + } + + template <typename T, typename U> + Status TestStaticBuiltins(const std::string& mock, U** object, + const std::unordered_set<std::string>& expected, + std::vector<std::string>* failed, + bool delete_objects = false) { + std::unordered_set<std::string> factories = expected; + Status s = TestCreateStatic<T>(mock, object, delete_objects); + EXPECT_NOK(s); +#ifndef ROCKSDB_LITE + std::vector<std::string> 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<T>(name, object, delete_objects); + if (!s.ok()) { + result = s; + failed->push_back(name); + } + } +#ifndef ROCKSDB_LITE + std::vector<std::string> 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<TableFactory>(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<TableFactory>(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<FileSystem>(DummyFileSystem::kClassName(), + FileSystem::kDefaultName())); + if (RegisterTests("Test")) { + auto fs = ExpectCreateShared<FileSystem>(DummyFileSystem::kClassName()); + ASSERT_FALSE(fs->IsInstanceOf(FileSystem::kDefaultName())); + } +} + +TEST_F(LoadCustomizableTest, LoadSecondaryCacheTest) { + ASSERT_OK( + TestSharedBuiltins<SecondaryCache>(TestSecondaryCache::kClassName(), "")); + if (RegisterTests("Test")) { + ExpectCreateShared<SecondaryCache>(TestSecondaryCache::kClassName()); + } +} + +#ifndef ROCKSDB_LITE +TEST_F(LoadCustomizableTest, LoadSstPartitionerFactoryTest) { + ASSERT_OK(TestSharedBuiltins<SstPartitionerFactory>( + "Mock", SstPartitionerFixedPrefixFactory::kClassName())); + if (RegisterTests("Test")) { + ExpectCreateShared<SstPartitionerFactory>("Mock"); + } +} +#endif // ROCKSDB_LITE + +TEST_F(LoadCustomizableTest, LoadChecksumGenFactoryTest) { + ASSERT_OK(TestSharedBuiltins<FileChecksumGenFactory>("Mock", "")); + if (RegisterTests("Test")) { + ExpectCreateShared<FileChecksumGenFactory>("Mock"); + } +} + +TEST_F(LoadCustomizableTest, LoadTablePropertiesCollectorFactoryTest) { + ASSERT_OK(TestSharedBuiltins<TablePropertiesCollectorFactory>( + MockTablePropertiesCollectorFactory::kClassName(), "")); + if (RegisterTests("Test")) { + ExpectCreateShared<TablePropertiesCollectorFactory>( + MockTablePropertiesCollectorFactory::kClassName()); + } +} + +TEST_F(LoadCustomizableTest, LoadComparatorTest) { + const Comparator* bytewise = BytewiseComparator(); + const Comparator* reverse = ReverseBytewiseComparator(); + const Comparator* result = nullptr; + std::unordered_set<std::string> expected = {bytewise->Name(), + reverse->Name()}; + std::vector<std::string> failures; + ASSERT_OK(TestStaticBuiltins<Comparator>( + test::SimpleSuffixReverseComparator::kClassName(), &result, expected, + &failures)); + if (RegisterTests("Test")) { + ASSERT_OK(TestCreateStatic<Comparator>( + test::SimpleSuffixReverseComparator::kClassName(), &result)); + } +} + +TEST_F(LoadCustomizableTest, LoadSliceTransformFactoryTest) { + std::shared_ptr<const SliceTransform> result; + std::vector<std::string> failures; + std::unordered_set<std::string> expected = {"rocksdb.Noop", "fixed", + "rocksdb.FixedPrefix", "capped", + "rocksdb.CappedPrefix"}; + ASSERT_OK(TestExpectedBuiltins<SliceTransform>( + "Mock", expected, &result, &failures, [](const std::string& name) { + std::vector<std::string> 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<SliceTransform>("Mock", &result); + } +} + +TEST_F(LoadCustomizableTest, LoadStatisticsTest) { + ASSERT_OK(TestSharedBuiltins<Statistics>(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<Statistics>(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<std::shared_ptr<Statistics>>( + "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<std::shared_ptr<Statistics>>("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<std::string> expected = { + SkipListFactory::kClassName(), + SkipListFactory::kNickName(), + }; + + std::vector<std::string> failures; + std::shared_ptr<MemTableRepFactory> factory; + Status s = TestExpectedBuiltins<MemTableRepFactory>( + "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<MemTableRepFactory>("SpecialSkipListFactory"); + } +} + +TEST_F(LoadCustomizableTest, LoadMergeOperatorTest) { + std::shared_ptr<MergeOperator> result; + std::vector<std::string> failed; + std::unordered_set<std::string> 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<MergeOperator>("Changling", expected, &result, + &failed)); + if (RegisterTests("Test")) { + ExpectCreateShared<MergeOperator>("Changling"); + } +} + +TEST_F(LoadCustomizableTest, LoadCompactionFilterFactoryTest) { + ASSERT_OK(TestSharedBuiltins<CompactionFilterFactory>("Changling", "")); + if (RegisterTests("Test")) { + ExpectCreateShared<CompactionFilterFactory>("Changling"); + } +} + +TEST_F(LoadCustomizableTest, LoadCompactionFilterTest) { + const CompactionFilter* result = nullptr; + std::vector<std::string> failures; + ASSERT_OK(TestStaticBuiltins<CompactionFilter>("Changling", &result, {}, + &failures, true)); + if (RegisterTests("Test")) { + ASSERT_OK(TestCreateStatic<CompactionFilter>("Changling", &result, true)); + } +} + +#ifndef ROCKSDB_LITE +TEST_F(LoadCustomizableTest, LoadEventListenerTest) { + ASSERT_OK(TestSharedBuiltins<EventListener>( + OnFileDeletionListener::kClassName(), "")); + if (RegisterTests("Test")) { + ExpectCreateShared<EventListener>(OnFileDeletionListener::kClassName()); + ExpectCreateShared<EventListener>(FlushCounterListener::kClassName()); + } +} + +TEST_F(LoadCustomizableTest, LoadEncryptionProviderTest) { + std::vector<std::string> failures; + std::shared_ptr<EncryptionProvider> result; + ASSERT_OK( + TestExpectedBuiltins<EncryptionProvider>("Mock", {}, &result, &failures)); + if (!failures.empty()) { + ASSERT_EQ(failures[0], "1://test"); + ASSERT_EQ(failures.size(), 1U); + } + + result = ExpectCreateShared<EncryptionProvider>("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<EncryptionProvider>("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<BlockCipher>("Mock", "ROT13")); + if (RegisterTests("Test")) { + ExpectCreateShared<BlockCipher>("Mock"); + } +} +#endif // !ROCKSDB_LITE + +TEST_F(LoadCustomizableTest, LoadSystemClockTest) { + ASSERT_OK(TestSharedBuiltins<SystemClock>(MockSystemClock::kClassName(), + SystemClock::kDefaultName())); + if (RegisterTests("Test")) { + auto result = + ExpectCreateShared<SystemClock>(MockSystemClock::kClassName()); + ASSERT_FALSE(result->IsInstanceOf(SystemClock::kDefaultName())); + } +} + +TEST_F(LoadCustomizableTest, LoadMemoryAllocatorTest) { + std::vector<std::string> failures; + Status s = TestSharedBuiltins<MemoryAllocator>( + 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<MemoryAllocator>(MockMemoryAllocator::kClassName()); + } +} + +TEST_F(LoadCustomizableTest, LoadFilterPolicyTest) { + const std::string kAutoBloom = BloomFilterPolicy::kClassName(); + const std::string kAutoRibbon = RibbonFilterPolicy::kClassName(); + + std::shared_ptr<const FilterPolicy> result; + std::vector<std::string> failures; + std::unordered_set<std::string> expected = { + ReadOnlyBuiltinFilterPolicy::kClassName(), + }; + +#ifndef ROCKSDB_LITE + expected.insert({ + kAutoBloom, + BloomFilterPolicy::kNickName(), + kAutoRibbon, + RibbonFilterPolicy::kNickName(), + }); +#endif // ROCKSDB_LITE + ASSERT_OK(TestExpectedBuiltins<const FilterPolicy>( + "Mock", expected, &result, &failures, [](const std::string& name) { + std::vector<std::string> 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<FilterPolicy>(MockFilterPolicy::kClassName(), &result); + } + + std::shared_ptr<TableFactory> 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<BlockBasedTableOptions>(); + 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<BlockBasedTableOptions>(); + 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<BlockBasedTableOptions>(); + 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<FlushBlockPolicyFactory> result; + std::shared_ptr<TableFactory> table; + std::vector<std::string> failed; + std::unordered_set<std::string> expected = { + FlushBlockBySizePolicyFactory::kClassName(), + FlushBlockEveryKeyPolicyFactory::kClassName(), + }; + + ASSERT_OK(TestExpectedBuiltins<FlushBlockPolicyFactory>( + 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<BlockBasedTableOptions>(); + 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<FlushBlockPolicyFactory>( + TestFlushBlockPolicyFactory::kClassName()); + ASSERT_OK(TableFactory::CreateFromString( + config_options_, table_opts + TestFlushBlockPolicyFactory::kClassName(), + &table)); + bbto = table->GetOptions<BlockBasedTableOptions>(); + 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(); +} |