diff --git a/storage/rocksdb/rocksdb/options/configurable_test.cc b/storage/rocksdb/rocksdb/options/configurable_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..fe0c76561ff89d75faf6a3f37354a2897d94653f --- /dev/null +++ b/storage/rocksdb/rocksdb/options/configurable_test.cc @@ -0,0 +1,757 @@ +// 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 { +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_; +}; + +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_)); + ConfigurableHelper::RegisterOptions(*this, name_ + "Unique", &unique_, + &unique_option_info); + } + if ((mode & TestConfigMode::kSharedMode) != 0) { + shared_.reset(SimpleConfigurable::Create("Shared" + name_)); + ConfigurableHelper::RegisterOptions(*this, name_ + "Shared", &shared_, + &shared_option_info); + } + if ((mode & TestConfigMode::kRawPtrMode) != 0) { + pointer_ = SimpleConfigurable::Create("Pointer" + name_); + ConfigurableHelper::RegisterOptions(*this, 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); +#ifndef ROCKSDB_LITE + 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"); +#endif +} + +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); +#ifndef ROCKSDB_LITE // GetOptionsFromMap is not supported in ROCKSDB_LITE + 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"); +#endif +} + +#ifndef ROCKSDB_LITE // GetOptionsFromMap is not supported in ROCKSDB_LITE +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 = ToString(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 = { +#ifndef ROCKSDB_LITE + {"validated", + {0, OptionType::kBoolean, OptionVerificationType::kNormal, + OptionTypeFlags::kNone}}, +#endif // ROCKSDB_LITE +}; +static std::unordered_map prepared_option_info = { +#ifndef ROCKSDB_LITE + {"prepared", + {0, OptionType::kInt, OptionVerificationType::kNormal, + OptionTypeFlags::kMutable}}, +#endif // ROCKSDB_LITE +}; +static std::unordered_map + dont_prepare_option_info = { +#ifndef ROCKSDB_LITE + {"unique", + {0, OptionType::kConfigurable, OptionVerificationType::kNormal, + (OptionTypeFlags::kUnique | OptionTypeFlags::kDontPrepare)}}, + +#endif // ROCKSDB_LITE +}; + +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) { + ConfigurableHelper::RegisterOptions(*this, "Validated", &validated, + &validated_option_info); + ConfigurableHelper::RegisterOptions(*this, "Prepared", &prepared, + &prepared_option_info); + if ((mode & TestConfigMode::kUniqueMode) != 0) { + unique_.reset(new ValidatedConfigurable( + "Unique" + name_, TestConfigMode::kDefaultMode, false)); + if (dont_prepare) { + ConfigurableHelper::RegisterOptions(*this, name_ + "Unique", &unique_, + &dont_prepare_option_info); + } else { + ConfigurableHelper::RegisterOptions(*this, 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, 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, 0}}}; + 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() { + static std::unordered_map struct_option_info = { +#ifndef ROCKSDB_LITE + {"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0, + OptionVerificationType::kNormal, + OptionTypeFlags::kMutable)}, +#endif // ROCKSDB_LITE + }; + 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")); +} + +#ifndef ROCKSDB_LITE +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)); +} +#endif + +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 (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;"))); +#endif // ROCKSDB_LITE + +} // namespace test +} // namespace ROCKSDB_NAMESPACE +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); +#ifdef GFLAGS + ParseCommandLineFlags(&argc, &argv, true); +#endif // GFLAGS + return RUN_ALL_TESTS(); +} diff --git a/storage/rocksdb/rocksdb/options/customizable.cc b/storage/rocksdb/rocksdb/options/customizable.cc new file mode 100644 index 0000000000000000000000000000000000000000..3488f326bac64d231dfd942f8d680b0e8a2448b1 --- /dev/null +++ b/storage/rocksdb/rocksdb/options/customizable.cc @@ -0,0 +1,77 @@ +// 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). + +#include "rocksdb/customizable.h" + +#include "options/configurable_helper.h" +#include "rocksdb/convenience.h" +#include "rocksdb/status.h" +#include "util/string_util.h" + +namespace ROCKSDB_NAMESPACE { + +std::string Customizable::GetOptionName(const std::string& long_name) const { + const std::string& name = Name(); + size_t name_len = name.size(); + if (long_name.size() > name_len + 1 && + long_name.compare(0, name_len, name) == 0 && + long_name.at(name_len) == '.') { + return long_name.substr(name_len + 1); + } else { + return Configurable::GetOptionName(long_name); + } +} + +#ifndef ROCKSDB_LITE +Status Customizable::GetOption(const ConfigOptions& config_options, + const std::string& opt_name, + std::string* value) const { + if (opt_name == ConfigurableHelper::kIdPropName) { + *value = GetId(); + return Status::OK(); + } else { + return Configurable::GetOption(config_options, opt_name, value); + } +} + +std::string Customizable::SerializeOptions(const ConfigOptions& config_options, + const std::string& prefix) const { + std::string result; + std::string parent; + if (!config_options.IsShallow()) { + parent = Configurable::SerializeOptions(config_options, ""); + } + if (parent.empty()) { + result = GetId(); + } else { + result.append(prefix + ConfigurableHelper::kIdPropName + "=" + GetId() + + config_options.delimiter); + result.append(parent); + } + return result; +} + +#endif // ROCKSDB_LITE + +bool Customizable::AreEquivalent(const ConfigOptions& config_options, + const Configurable* other, + std::string* mismatch) const { + if (config_options.sanity_level > ConfigOptions::kSanityLevelNone && + this != other) { + const Customizable* custom = reinterpret_cast(other); + if (GetId() != custom->GetId()) { + *mismatch = ConfigurableHelper::kIdPropName; + return false; + } else if (config_options.sanity_level > + ConfigOptions::kSanityLevelLooselyCompatible) { + bool matches = + Configurable::AreEquivalent(config_options, other, mismatch); + return matches; + } + } + return true; +} + +} // namespace ROCKSDB_NAMESPACE