// 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 "options/configurable_test.h" #include #include #include #include #include "options/configurable_helper.h" #include "options/options_helper.h" #include "options/options_parser.h" #include "rocksdb/configurable.h" #include "test_util/testharness.h" #include "test_util/testutil.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::test { 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_; }; static std::unordered_map struct_option_info = { {"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0, OptionVerificationType::kNormal, OptionTypeFlags::kMutable)}, }; static std::unordered_map imm_struct_option_info = { {"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone)}, }; class SimpleConfigurable : public TestConfigurable { public: static SimpleConfigurable* Create( const std::string& name = "simple", int mode = TestConfigMode::kDefaultMode, const std::unordered_map* map = &simple_option_info) { return new SimpleConfigurable(name, mode, map); } SimpleConfigurable(const std::string& name, int mode, const std::unordered_map* map = &simple_option_info) : TestConfigurable(name, mode, map) { if ((mode & TestConfigMode::kUniqueMode) != 0) { unique_.reset(SimpleConfigurable::Create("Unique" + name_)); RegisterOptions(name_ + "Unique", &unique_, &unique_option_info); } if ((mode & TestConfigMode::kSharedMode) != 0) { shared_.reset(SimpleConfigurable::Create("Shared" + name_)); RegisterOptions(name_ + "Shared", &shared_, &shared_option_info); } if ((mode & TestConfigMode::kRawPtrMode) != 0) { pointer_ = SimpleConfigurable::Create("Pointer" + name_); RegisterOptions(name_ + "Pointer", &pointer_, &pointer_option_info); } } }; // End class SimpleConfigurable using ConfigTestFactoryFunc = std::function; class ConfigurableTest : public testing::Test { public: ConfigurableTest() { config_options_.invoke_prepare_options = false; } ConfigOptions config_options_; }; TEST_F(ConfigurableTest, GetOptionsPtrTest) { std::string opt_str; std::unique_ptr configurable(SimpleConfigurable::Create()); ASSERT_NE(configurable->GetOptions("simple"), nullptr); ASSERT_EQ(configurable->GetOptions("bad-opt"), nullptr); } TEST_F(ConfigurableTest, ConfigureFromMapTest) { std::unique_ptr configurable(SimpleConfigurable::Create()); auto* opts = configurable->GetOptions("simple"); ASSERT_OK(configurable->ConfigureFromMap(config_options_, {})); ASSERT_NE(opts, nullptr); std::unordered_map options_map = { {"int", "1"}, {"bool", "true"}, {"string", "string"}}; ASSERT_OK(configurable->ConfigureFromMap(config_options_, options_map)); ASSERT_EQ(opts->i, 1); ASSERT_EQ(opts->b, true); ASSERT_EQ(opts->s, "string"); } TEST_F(ConfigurableTest, ConfigureFromStringTest) { std::unique_ptr configurable(SimpleConfigurable::Create()); auto* opts = configurable->GetOptions("simple"); ASSERT_OK(configurable->ConfigureFromString(config_options_, "")); ASSERT_NE(opts, nullptr); ASSERT_OK(configurable->ConfigureFromString(config_options_, "int=1;bool=true;string=s")); ASSERT_EQ(opts->i, 1); ASSERT_EQ(opts->b, true); ASSERT_EQ(opts->s, "s"); } TEST_F(ConfigurableTest, ConfigureIgnoreTest) { std::unique_ptr configurable(SimpleConfigurable::Create()); std::unordered_map options_map = {{"unused", "u"}}; ConfigOptions ignore = config_options_; ignore.ignore_unknown_options = true; ASSERT_NOK(configurable->ConfigureFromMap(config_options_, options_map)); ASSERT_OK(configurable->ConfigureFromMap(ignore, options_map)); ASSERT_NOK(configurable->ConfigureFromString(config_options_, "unused=u")); ASSERT_OK(configurable->ConfigureFromString(ignore, "unused=u")); } TEST_F(ConfigurableTest, ConfigureNestedOptionsTest) { std::unique_ptr base, copy; std::string opt_str; std::string mismatch; base.reset(SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); copy.reset(SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); ASSERT_OK(base->ConfigureFromString(config_options_, "shared={int=10; string=10};" "unique={int=20; string=20};" "pointer={int=30; string=30};")); 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)); } TEST_F(ConfigurableTest, GetOptionsTest) { std::unique_ptr simple; simple.reset( SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); int i = 11; for (auto opt : {"", "shared.", "unique.", "pointer."}) { std::string value; std::string expected = std::to_string(i); std::string opt_name = opt; ASSERT_OK( simple->ConfigureOption(config_options_, opt_name + "int", expected)); ASSERT_OK(simple->GetOption(config_options_, opt_name + "int", &value)); ASSERT_EQ(expected, value); ASSERT_OK(simple->ConfigureOption(config_options_, opt_name + "string", expected)); ASSERT_OK(simple->GetOption(config_options_, opt_name + "string", &value)); ASSERT_EQ(expected, value); ASSERT_NOK( simple->ConfigureOption(config_options_, opt_name + "bad", expected)); ASSERT_NOK(simple->GetOption(config_options_, "bad option", &value)); ASSERT_TRUE(value.empty()); i += 11; } } TEST_F(ConfigurableTest, ConfigureBadOptionsTest) { std::unique_ptr configurable(SimpleConfigurable::Create()); auto* opts = configurable->GetOptions("simple"); ASSERT_NE(opts, nullptr); ASSERT_OK(configurable->ConfigureOption(config_options_, "int", "42")); ASSERT_EQ(opts->i, 42); ASSERT_NOK(configurable->ConfigureOption(config_options_, "int", "fred")); ASSERT_NOK(configurable->ConfigureOption(config_options_, "bool", "fred")); ASSERT_NOK( configurable->ConfigureFromString(config_options_, "int=33;unused=u")); ASSERT_EQ(opts->i, 42); } TEST_F(ConfigurableTest, InvalidOptionTest) { std::unique_ptr configurable(SimpleConfigurable::Create()); std::unordered_map options_map = { {"bad-option", "bad"}}; ASSERT_NOK(configurable->ConfigureFromMap(config_options_, options_map)); ASSERT_NOK( configurable->ConfigureFromString(config_options_, "bad-option=bad")); ASSERT_NOK( configurable->ConfigureOption(config_options_, "bad-option", "bad")); } static std::unordered_map validated_option_info = { {"validated", {0, OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, }; static std::unordered_map prepared_option_info = { {"prepared", {0, OptionType::kInt, OptionVerificationType::kNormal, OptionTypeFlags::kMutable}}, }; static std::unordered_map dont_prepare_option_info = { {"unique", {0, OptionType::kConfigurable, OptionVerificationType::kNormal, (OptionTypeFlags::kUnique | OptionTypeFlags::kDontPrepare)}}, }; class ValidatedConfigurable : public SimpleConfigurable { public: ValidatedConfigurable(const std::string& name, unsigned char mode, bool dont_prepare = false) : SimpleConfigurable(name, TestConfigMode::kDefaultMode), validated(false), prepared(0) { RegisterOptions("Validated", &validated, &validated_option_info); RegisterOptions("Prepared", &prepared, &prepared_option_info); if ((mode & TestConfigMode::kUniqueMode) != 0) { unique_.reset(new ValidatedConfigurable( "Unique" + name_, TestConfigMode::kDefaultMode, false)); if (dont_prepare) { RegisterOptions(name_ + "Unique", &unique_, &dont_prepare_option_info); } else { RegisterOptions(name_ + "Unique", &unique_, &unique_option_info); } } } Status PrepareOptions(const ConfigOptions& config_options) override { if (++prepared <= 0) { return Status::InvalidArgument("Cannot prepare option"); } else { return SimpleConfigurable::PrepareOptions(config_options); } } Status ValidateOptions(const DBOptions& db_opts, const ColumnFamilyOptions& cf_opts) const override { if (!validated) { return Status::InvalidArgument("Not Validated"); } else { return SimpleConfigurable::ValidateOptions(db_opts, cf_opts); } } private: bool validated; int prepared; }; TEST_F(ConfigurableTest, ValidateOptionsTest) { std::unique_ptr configurable( new ValidatedConfigurable("validated", TestConfigMode::kDefaultMode)); ColumnFamilyOptions cf_opts; DBOptions db_opts; ASSERT_OK( configurable->ConfigureOption(config_options_, "validated", "false")); ASSERT_NOK(configurable->ValidateOptions(db_opts, cf_opts)); ASSERT_OK( configurable->ConfigureOption(config_options_, "validated", "true")); ASSERT_OK(configurable->ValidateOptions(db_opts, cf_opts)); } TEST_F(ConfigurableTest, PrepareOptionsTest) { std::unique_ptr c( new ValidatedConfigurable("Simple", TestConfigMode::kUniqueMode, false)); auto cp = c->GetOptions("Prepared"); auto u = c->GetOptions>("SimpleUnique"); auto up = u->get()->GetOptions("Prepared"); config_options_.invoke_prepare_options = false; ASSERT_NE(cp, nullptr); ASSERT_NE(up, nullptr); ASSERT_EQ(*cp, 0); ASSERT_EQ(*up, 0); ASSERT_OK(c->ConfigureFromMap(config_options_, {})); ASSERT_EQ(*cp, 0); ASSERT_EQ(*up, 0); config_options_.invoke_prepare_options = true; ASSERT_OK(c->ConfigureFromMap(config_options_, {})); ASSERT_EQ(*cp, 1); ASSERT_EQ(*up, 1); ASSERT_OK(c->ConfigureFromString(config_options_, "prepared=0")); ASSERT_EQ(*up, 2); ASSERT_EQ(*cp, 1); ASSERT_NOK(c->ConfigureFromString(config_options_, "prepared=-2")); c.reset( new ValidatedConfigurable("Simple", TestConfigMode::kUniqueMode, true)); cp = c->GetOptions("Prepared"); u = c->GetOptions>("SimpleUnique"); up = u->get()->GetOptions("Prepared"); ASSERT_OK(c->ConfigureFromString(config_options_, "prepared=0")); ASSERT_EQ(*cp, 1); ASSERT_EQ(*up, 0); } TEST_F(ConfigurableTest, CopyObjectTest) { class CopyConfigurable : public Configurable { public: CopyConfigurable() : prepared_(0), validated_(0) {} Status PrepareOptions(const ConfigOptions& options) override { prepared_++; return Configurable::PrepareOptions(options); } Status ValidateOptions(const DBOptions& db_opts, const ColumnFamilyOptions& cf_opts) const override { validated_++; return Configurable::ValidateOptions(db_opts, cf_opts); } int prepared_; mutable int validated_; }; CopyConfigurable 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); CopyConfigurable 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(ConfigurableTest, MutableOptionsTest) { static std::unordered_map imm_option_info = { {"imm", OptionTypeInfo::Struct("imm", &simple_option_info, 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone)}, }; class MutableConfigurable : public SimpleConfigurable { public: MutableConfigurable() : SimpleConfigurable("mutable", TestConfigMode::kDefaultMode | TestConfigMode::kUniqueMode | TestConfigMode::kSharedMode) { RegisterOptions("struct", &options_, &struct_option_info); RegisterOptions("imm", &options_, &imm_option_info); } }; MutableConfigurable mc; ConfigOptions options = config_options_; ASSERT_OK(mc.ConfigureOption(options, "bool", "true")); ASSERT_OK(mc.ConfigureOption(options, "int", "42")); auto* opts = mc.GetOptions("mutable"); ASSERT_NE(opts, nullptr); ASSERT_EQ(opts->i, 42); ASSERT_EQ(opts->b, true); ASSERT_OK(mc.ConfigureOption(options, "struct", "{bool=false;}")); ASSERT_OK(mc.ConfigureOption(options, "imm", "{int=55;}")); options.mutable_options_only = true; // Now only mutable options should be settable. ASSERT_NOK(mc.ConfigureOption(options, "bool", "true")); ASSERT_OK(mc.ConfigureOption(options, "int", "24")); ASSERT_EQ(opts->i, 24); ASSERT_EQ(opts->b, false); ASSERT_NOK(mc.ConfigureFromString(options, "bool=false;int=33;")); ASSERT_EQ(opts->i, 24); ASSERT_EQ(opts->b, false); // Setting options through an immutable struct fails ASSERT_NOK(mc.ConfigureOption(options, "imm", "{int=55;}")); ASSERT_NOK(mc.ConfigureOption(options, "imm.int", "55")); ASSERT_EQ(opts->i, 24); ASSERT_EQ(opts->b, false); // Setting options through an mutable struct succeeds ASSERT_OK(mc.ConfigureOption(options, "struct", "{int=44;}")); ASSERT_EQ(opts->i, 44); ASSERT_OK(mc.ConfigureOption(options, "struct.int", "55")); ASSERT_EQ(opts->i, 55); // Setting nested immutable configurable options fail ASSERT_NOK(mc.ConfigureOption(options, "shared", "{bool=true;}")); ASSERT_NOK(mc.ConfigureOption(options, "shared.bool", "true")); // Setting nested mutable configurable options succeeds ASSERT_OK(mc.ConfigureOption(options, "unique", "{bool=true}")); ASSERT_OK(mc.ConfigureOption(options, "unique.bool", "true")); } TEST_F(ConfigurableTest, DeprecatedOptionsTest) { static std::unordered_map deprecated_option_info = { {"deprecated", {offsetof(struct TestOptions, b), OptionType::kBoolean, OptionVerificationType::kDeprecated, OptionTypeFlags::kNone}}}; std::unique_ptr orig; orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode, &deprecated_option_info)); auto* opts = orig->GetOptions("simple"); ASSERT_NE(opts, nullptr); opts->d = true; ASSERT_OK(orig->ConfigureOption(config_options_, "deprecated", "false")); ASSERT_TRUE(opts->d); ASSERT_OK(orig->ConfigureFromString(config_options_, "deprecated=false")); ASSERT_TRUE(opts->d); } TEST_F(ConfigurableTest, AliasOptionsTest) { static std::unordered_map alias_option_info = { {"bool", {offsetof(struct TestOptions, b), OptionType::kBoolean, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, {"alias", {offsetof(struct TestOptions, b), OptionType::kBoolean, OptionVerificationType::kAlias, OptionTypeFlags::kNone, nullptr}}}; std::unique_ptr orig; orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode, &alias_option_info)); auto* opts = orig->GetOptions("simple"); ASSERT_NE(opts, nullptr); ASSERT_OK(orig->ConfigureOption(config_options_, "bool", "false")); ASSERT_FALSE(opts->b); ASSERT_OK(orig->ConfigureOption(config_options_, "alias", "true")); ASSERT_TRUE(opts->b); std::string opts_str; ASSERT_OK(orig->GetOptionString(config_options_, &opts_str)); ASSERT_EQ(opts_str.find("alias"), std::string::npos); ASSERT_OK(orig->ConfigureOption(config_options_, "bool", "false")); ASSERT_FALSE(opts->b); ASSERT_OK(orig->GetOption(config_options_, "alias", &opts_str)); ASSERT_EQ(opts_str, "false"); } TEST_F(ConfigurableTest, NestedUniqueConfigTest) { std::unique_ptr simple; simple.reset( SimpleConfigurable::Create("Outer", TestConfigMode::kAllOptMode)); const auto outer = simple->GetOptions("Outer"); const auto unique = simple->GetOptions>("OuterUnique"); ASSERT_NE(outer, nullptr); ASSERT_NE(unique, nullptr); ASSERT_OK( simple->ConfigureFromString(config_options_, "int=24;string=outer")); ASSERT_OK(simple->ConfigureFromString(config_options_, "unique={int=42;string=nested}")); const auto inner = unique->get()->GetOptions("UniqueOuter"); ASSERT_NE(inner, nullptr); ASSERT_EQ(outer->i, 24); ASSERT_EQ(outer->s, "outer"); ASSERT_EQ(inner->i, 42); ASSERT_EQ(inner->s, "nested"); } TEST_F(ConfigurableTest, NestedSharedConfigTest) { std::unique_ptr simple; simple.reset(SimpleConfigurable::Create( "Outer", TestConfigMode::kDefaultMode | TestConfigMode::kSharedMode)); ASSERT_OK( simple->ConfigureFromString(config_options_, "int=24;string=outer")); ASSERT_OK(simple->ConfigureFromString(config_options_, "shared={int=42;string=nested}")); const auto outer = simple->GetOptions("Outer"); const auto shared = simple->GetOptions>("OuterShared"); ASSERT_NE(outer, nullptr); ASSERT_NE(shared, nullptr); const auto inner = shared->get()->GetOptions("SharedOuter"); ASSERT_NE(inner, nullptr); ASSERT_EQ(outer->i, 24); ASSERT_EQ(outer->s, "outer"); ASSERT_EQ(inner->i, 42); ASSERT_EQ(inner->s, "nested"); } TEST_F(ConfigurableTest, NestedRawConfigTest) { std::unique_ptr simple; simple.reset(SimpleConfigurable::Create( "Outer", TestConfigMode::kDefaultMode | TestConfigMode::kRawPtrMode)); ASSERT_OK( simple->ConfigureFromString(config_options_, "int=24;string=outer")); ASSERT_OK(simple->ConfigureFromString(config_options_, "pointer={int=42;string=nested}")); const auto outer = simple->GetOptions("Outer"); const auto pointer = simple->GetOptions("OuterPointer"); ASSERT_NE(outer, nullptr); ASSERT_NE(pointer, nullptr); const auto inner = (*pointer)->GetOptions("PointerOuter"); ASSERT_NE(inner, nullptr); ASSERT_EQ(outer->i, 24); ASSERT_EQ(outer->s, "outer"); ASSERT_EQ(inner->i, 42); ASSERT_EQ(inner->s, "nested"); } TEST_F(ConfigurableTest, MatchesTest) { std::string mismatch; std::unique_ptr base, copy; base.reset(SimpleConfigurable::Create( "simple", TestConfigMode::kDefaultMode | TestConfigMode::kNestedMode)); copy.reset(SimpleConfigurable::Create( "simple", TestConfigMode::kDefaultMode | TestConfigMode::kNestedMode)); ASSERT_OK(base->ConfigureFromString( config_options_, "int=11;string=outer;unique={int=22;string=u};shared={int=33;string=s}")); ASSERT_OK(copy->ConfigureFromString( config_options_, "int=11;string=outer;unique={int=22;string=u};shared={int=33;string=s}")); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_OK(base->ConfigureOption(config_options_, "shared", "int=44")); ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_EQ(mismatch, "shared.int"); std::string c1value, c2value; ASSERT_OK(base->GetOption(config_options_, mismatch, &c1value)); ASSERT_OK(copy->GetOption(config_options_, mismatch, &c2value)); ASSERT_NE(c1value, c2value); } static Configurable* SimpleStructFactory() { return SimpleConfigurable::Create( "simple-struct", TestConfigMode::kDefaultMode, &struct_option_info); } TEST_F(ConfigurableTest, ConfigureStructTest) { std::unique_ptr base(SimpleStructFactory()); std::unique_ptr copy(SimpleStructFactory()); std::string opt_str, value; std::string mismatch; std::unordered_set names; ASSERT_OK( base->ConfigureFromString(config_options_, "struct={int=10; string=10}")); 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)); ASSERT_OK(base->GetOptionNames(config_options_, &names)); ASSERT_EQ(names.size(), 1); ASSERT_EQ(*(names.begin()), "struct"); ASSERT_OK( base->ConfigureFromString(config_options_, "struct={int=20; string=20}")); ASSERT_OK(base->GetOption(config_options_, "struct", &value)); ASSERT_OK(copy->ConfigureOption(config_options_, "struct", value)); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_NOK(base->ConfigureFromString(config_options_, "struct={int=10; string=10; bad=11}")); ASSERT_OK(base->ConfigureOption(config_options_, "struct.int", "42")); ASSERT_NOK(base->ConfigureOption(config_options_, "struct.bad", "42")); ASSERT_NOK(base->GetOption(config_options_, "struct.bad", &value)); ASSERT_OK(base->GetOption(config_options_, "struct.int", &value)); ASSERT_EQ(value, "42"); } TEST_F(ConfigurableTest, ConfigurableEnumTest) { std::unique_ptr base, copy; base.reset(SimpleConfigurable::Create("e", TestConfigMode::kEnumMode)); copy.reset(SimpleConfigurable::Create("e", TestConfigMode::kEnumMode)); std::string opts_str; std::string mismatch; ASSERT_OK(base->ConfigureFromString(config_options_, "enum=B")); ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_OK(base->GetOptionString(config_options_, &opts_str)); ASSERT_OK(copy->ConfigureFromString(config_options_, opts_str)); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_NOK(base->ConfigureOption(config_options_, "enum", "bad")); ASSERT_NOK(base->ConfigureOption(config_options_, "unknown", "bad")); } static std::unordered_map noserialize_option_info = { {"int", {offsetof(struct TestOptions, i), OptionType::kInt, OptionVerificationType::kNormal, OptionTypeFlags::kDontSerialize}}, }; TEST_F(ConfigurableTest, TestNoSerialize) { std::unique_ptr base; base.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, &noserialize_option_info)); std::string opts_str, value; ASSERT_OK(base->ConfigureFromString(config_options_, "int=10")); ASSERT_OK(base->GetOptionString(config_options_, &opts_str)); ASSERT_EQ(opts_str, ""); ASSERT_NOK(base->GetOption(config_options_, "int", &value)); } TEST_F(ConfigurableTest, TestNoCompare) { std::unordered_map nocomp_option_info = { {"int", {offsetof(struct TestOptions, i), OptionType::kInt, OptionVerificationType::kNormal, OptionTypeFlags::kCompareNever}}, }; std::unordered_map normal_option_info = { {"int", {offsetof(struct TestOptions, i), OptionType::kInt, OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, }; std::unique_ptr base, copy; base.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, &nocomp_option_info)); copy.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, &normal_option_info)); ASSERT_OK(base->ConfigureFromString(config_options_, "int=10")); ASSERT_OK(copy->ConfigureFromString(config_options_, "int=20")); std::string bvalue, cvalue, mismatch; ASSERT_OK(base->GetOption(config_options_, "int", &bvalue)); ASSERT_OK(copy->GetOption(config_options_, "int", &cvalue)); ASSERT_EQ(bvalue, "10"); ASSERT_EQ(cvalue, "20"); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); ASSERT_FALSE(copy->AreEquivalent(config_options_, base.get(), &mismatch)); } TEST_F(ConfigurableTest, NullOptionMapTest) { std::unique_ptr base; std::unordered_set names; std::string str; base.reset( SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, nullptr)); ASSERT_NOK(base->ConfigureFromString(config_options_, "int=10")); ASSERT_NOK(base->ConfigureFromString(config_options_, "int=20")); ASSERT_NOK(base->ConfigureOption(config_options_, "int", "20")); ASSERT_NOK(base->GetOption(config_options_, "int", &str)); ASSERT_NE(base->GetOptions("c"), nullptr); ASSERT_OK(base->GetOptionNames(config_options_, &names)); ASSERT_EQ(names.size(), 0UL); ASSERT_OK(base->PrepareOptions(config_options_)); ASSERT_OK(base->ValidateOptions(DBOptions(), ColumnFamilyOptions())); std::unique_ptr copy; copy.reset( SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, nullptr)); ASSERT_OK(base->GetOptionString(config_options_, &str)); ASSERT_OK(copy->ConfigureFromString(config_options_, str)); ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &str)); } static std::unordered_map TestFactories = { {"Simple", []() { return SimpleConfigurable::Create("simple"); }}, {"Struct", []() { return SimpleStructFactory(); }}, {"Unique", []() { return SimpleConfigurable::Create( "simple", TestConfigMode::kSimpleMode | TestConfigMode::kUniqueMode); }}, {"Shared", []() { return SimpleConfigurable::Create( "simple", TestConfigMode::kSimpleMode | TestConfigMode::kSharedMode); }}, {"Nested", []() { return SimpleConfigurable::Create( "simple", TestConfigMode::kSimpleMode | TestConfigMode::kNestedMode); }}, {"Mutable", []() { return SimpleConfigurable::Create("simple", TestConfigMode::kMutableMode | TestConfigMode::kSimpleMode | TestConfigMode::kNestedMode); }}, {"ThreeDeep", []() { Configurable* simple = SimpleConfigurable::Create( "Simple", TestConfigMode::kUniqueMode | TestConfigMode::kDefaultMode); auto* unique = simple->GetOptions>("SimpleUnique"); unique->reset(SimpleConfigurable::Create( "Child", TestConfigMode::kUniqueMode | TestConfigMode::kDefaultMode)); unique = unique->get()->GetOptions>( "ChildUnique"); unique->reset( SimpleConfigurable::Create("Child", TestConfigMode::kDefaultMode)); return simple; }}, {"DBOptions", []() { auto config = DBOptionsAsConfigurable(DBOptions()); return config.release(); }}, {"CFOptions", []() { auto config = CFOptionsAsConfigurable(ColumnFamilyOptions()); return config.release(); }}, {"BlockBased", []() { return NewBlockBasedTableFactory(); }}, }; class ConfigurableParamTest : public ConfigurableTest, virtual public ::testing::WithParamInterface< std::pair> { public: ConfigurableParamTest() { type_ = GetParam().first; configuration_ = GetParam().second; assert(TestFactories.find(type_) != TestFactories.end()); object_.reset(CreateConfigurable()); } Configurable* CreateConfigurable() { const auto& iter = TestFactories.find(type_); return (iter->second)(); } void TestConfigureOptions(const ConfigOptions& opts); std::string type_; std::string configuration_; std::unique_ptr object_; }; void ConfigurableParamTest::TestConfigureOptions( const ConfigOptions& config_options) { std::unique_ptr base, copy; std::unordered_set names; std::string opt_str, mismatch; base.reset(CreateConfigurable()); copy.reset(CreateConfigurable()); ASSERT_OK(base->ConfigureFromString(config_options, configuration_)); ASSERT_OK(base->GetOptionString(config_options, &opt_str)); ASSERT_OK(copy->ConfigureFromString(config_options, opt_str)); ASSERT_OK(copy->GetOptionString(config_options, &opt_str)); ASSERT_TRUE(base->AreEquivalent(config_options, copy.get(), &mismatch)); copy.reset(CreateConfigurable()); ASSERT_OK(base->GetOptionNames(config_options, &names)); std::unordered_map unused; bool found_one = false; for (const auto& name : names) { std::string value; Status s = base->GetOption(config_options, name, &value); if (s.ok()) { s = copy->ConfigureOption(config_options, name, value); if (s.ok() || s.IsNotSupported()) { found_one = true; } else { unused[name] = value; } } else { ASSERT_TRUE(s.IsNotSupported()); } } ASSERT_TRUE(found_one || names.empty()); while (found_one && !unused.empty()) { found_one = false; for (auto iter = unused.begin(); iter != unused.end();) { if (copy->ConfigureOption(config_options, iter->first, iter->second) .ok()) { found_one = true; iter = unused.erase(iter); } else { ++iter; } } } ASSERT_EQ(0, unused.size()); ASSERT_TRUE(base->AreEquivalent(config_options, copy.get(), &mismatch)); } TEST_P(ConfigurableParamTest, GetDefaultOptionsTest) { TestConfigureOptions(config_options_); } TEST_P(ConfigurableParamTest, ConfigureFromPropsTest) { std::string opt_str, mismatch; std::unordered_set names; std::unique_ptr copy(CreateConfigurable()); ASSERT_OK(object_->ConfigureFromString(config_options_, configuration_)); config_options_.delimiter = "\n"; ASSERT_OK(object_->GetOptionString(config_options_, &opt_str)); std::istringstream iss(opt_str); std::unordered_map copy_map; std::string line; 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)); copy_map[name] = value; } ASSERT_OK(copy->ConfigureFromMap(config_options_, copy_map)); ASSERT_TRUE(object_->AreEquivalent(config_options_, copy.get(), &mismatch)); } INSTANTIATE_TEST_CASE_P( ParamTest, ConfigurableParamTest, testing::Values( std::pair("Simple", "int=42;bool=true;string=s"), std::pair( "Mutable", "int=42;unique={int=33;string=unique}"), std::pair( "Struct", "struct={int=33;bool=true;string=s;}"), std::pair("Shared", "int=33;bool=true;string=outer;" "shared={int=42;string=shared}"), std::pair("Unique", "int=33;bool=true;string=outer;" "unique={int=42;string=unique}"), std::pair("Nested", "int=11;bool=true;string=outer;" "pointer={int=22;string=pointer};" "unique={int=33;string=unique};" "shared={int=44;string=shared}"), std::pair("ThreeDeep", "int=11;bool=true;string=outer;" "unique={int=22;string=inner;" "unique={int=33;string=unique}};"), std::pair("DBOptions", "max_background_jobs=100;" "max_open_files=200;"), std::pair("CFOptions", "table_factory=BlockBasedTable;" "disable_auto_compactions=true;"), std::pair("BlockBased", "block_size=1024;" "no_block_cache=true;"))); } // namespace ROCKSDB_NAMESPACE::test int main(int argc, char** argv) { ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); ::testing::InitGoogleTest(&argc, argv); #ifdef GFLAGS ParseCommandLineFlags(&argc, &argv, true); #endif // GFLAGS return RUN_ALL_TESTS(); }