summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/options/customizable_test.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/rocksdb/options/customizable_test.cc2255
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", &copy));
+ 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_, &copy, &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();
+}