rocksdb/options/configurable_test.cc
mrambacher 3efc6fd641 Make Configurable/Customizable options copyable (#8704)
Summary:
The atomic variable "is_prepared_" was keeping Configurable objects from being copy-constructed.  Removed the atomic to allow copies.

Since the variable is only changed from false to true (and never back), there is no reason it had to be atomic.

Added tests that simple Configurable and Customizable objects can be put on the stack and copied.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/8704

Reviewed By: anand1976

Differential Revision: D30530526

Pulled By: ltamasi

fbshipit-source-id: 4dd4439b3e5ad7fa396573d0b25d9fb709160576
2021-08-25 17:51:50 -07:00

857 lines
33 KiB
C++

// 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 <cctype>
#include <cinttypes>
#include <cstring>
#include <unordered_map>
#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_;
};
static std::unordered_map<std::string, OptionTypeInfo> struct_option_info = {
#ifndef ROCKSDB_LITE
{"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0,
OptionVerificationType::kNormal,
OptionTypeFlags::kMutable)},
#endif // ROCKSDB_LITE
};
static std::unordered_map<std::string, OptionTypeInfo> imm_struct_option_info =
{
#ifndef ROCKSDB_LITE
{"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0,
OptionVerificationType::kNormal,
OptionTypeFlags::kNone)},
#endif // ROCKSDB_LITE
};
class SimpleConfigurable : public TestConfigurable<Configurable> {
public:
static SimpleConfigurable* Create(
const std::string& name = "simple",
int mode = TestConfigMode::kDefaultMode,
const std::unordered_map<std::string, OptionTypeInfo>* map =
&simple_option_info) {
return new SimpleConfigurable(name, mode, map);
}
SimpleConfigurable(const std::string& name, int mode,
const std::unordered_map<std::string, OptionTypeInfo>*
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<Configurable*()>;
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> configurable(SimpleConfigurable::Create());
ASSERT_NE(configurable->GetOptions<TestOptions>("simple"), nullptr);
ASSERT_EQ(configurable->GetOptions<TestOptions>("bad-opt"), nullptr);
}
TEST_F(ConfigurableTest, ConfigureFromMapTest) {
std::unique_ptr<Configurable> configurable(SimpleConfigurable::Create());
auto* opts = configurable->GetOptions<TestOptions>("simple");
ASSERT_OK(configurable->ConfigureFromMap(config_options_, {}));
ASSERT_NE(opts, nullptr);
#ifndef ROCKSDB_LITE
std::unordered_map<std::string, std::string> 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> configurable(SimpleConfigurable::Create());
auto* opts = configurable->GetOptions<TestOptions>("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> configurable(SimpleConfigurable::Create());
std::unordered_map<std::string, std::string> 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<Configurable> 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<Configurable> 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> configurable(SimpleConfigurable::Create());
auto* opts = configurable->GetOptions<TestOptions>("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> configurable(SimpleConfigurable::Create());
std::unordered_map<std::string, std::string> 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<std::string, OptionTypeInfo> validated_option_info = {
#ifndef ROCKSDB_LITE
{"validated",
{0, OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
#endif // ROCKSDB_LITE
};
static std::unordered_map<std::string, OptionTypeInfo> prepared_option_info = {
#ifndef ROCKSDB_LITE
{"prepared",
{0, OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
#endif // ROCKSDB_LITE
};
static std::unordered_map<std::string, OptionTypeInfo>
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) {
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> 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<Configurable> c(
new ValidatedConfigurable("Simple", TestConfigMode::kUniqueMode, false));
auto cp = c->GetOptions<int>("Prepared");
auto u = c->GetOptions<std::unique_ptr<Configurable>>("SimpleUnique");
auto up = u->get()->GetOptions<int>("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<int>("Prepared");
u = c->GetOptions<std::unique_ptr<Configurable>>("SimpleUnique");
up = u->get()->GetOptions<int>("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<std::string, OptionTypeInfo> imm_option_info = {
#ifndef ROCKSDB_LITE
{"imm", OptionTypeInfo::Struct("imm", &simple_option_info, 0,
OptionVerificationType::kNormal,
OptionTypeFlags::kNone)},
#endif // ROCKSDB_LITE
};
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<TestOptions>("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<std::string, OptionTypeInfo>
deprecated_option_info = {
{"deprecated",
{offsetof(struct TestOptions, b), OptionType::kBoolean,
OptionVerificationType::kDeprecated, OptionTypeFlags::kNone}}};
std::unique_ptr<Configurable> orig;
orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode,
&deprecated_option_info));
auto* opts = orig->GetOptions<TestOptions>("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<std::string, OptionTypeInfo> 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<Configurable> orig;
orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode,
&alias_option_info));
auto* opts = orig->GetOptions<TestOptions>("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<Configurable> simple;
simple.reset(
SimpleConfigurable::Create("Outer", TestConfigMode::kAllOptMode));
const auto outer = simple->GetOptions<TestOptions>("Outer");
const auto unique =
simple->GetOptions<std::unique_ptr<Configurable>>("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<TestOptions>("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<Configurable> 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<TestOptions>("Outer");
const auto shared =
simple->GetOptions<std::shared_ptr<Configurable>>("OuterShared");
ASSERT_NE(outer, nullptr);
ASSERT_NE(shared, nullptr);
const auto inner = shared->get()->GetOptions<TestOptions>("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<Configurable> 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<TestOptions>("Outer");
const auto pointer = simple->GetOptions<Configurable*>("OuterPointer");
ASSERT_NE(outer, nullptr);
ASSERT_NE(pointer, nullptr);
const auto inner = (*pointer)->GetOptions<TestOptions>("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<Configurable> 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<Configurable> base(SimpleStructFactory());
std::unique_ptr<Configurable> copy(SimpleStructFactory());
std::string opt_str, value;
std::string mismatch;
std::unordered_set<std::string> 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<Configurable> 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<std::string, OptionTypeInfo> noserialize_option_info =
{
{"int",
{offsetof(struct TestOptions, i), OptionType::kInt,
OptionVerificationType::kNormal, OptionTypeFlags::kDontSerialize}},
};
TEST_F(ConfigurableTest, TestNoSerialize) {
std::unique_ptr<Configurable> 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<std::string, OptionTypeInfo> nocomp_option_info = {
{"int",
{offsetof(struct TestOptions, i), OptionType::kInt,
OptionVerificationType::kNormal, OptionTypeFlags::kCompareNever}},
};
std::unordered_map<std::string, OptionTypeInfo> normal_option_info = {
{"int",
{offsetof(struct TestOptions, i), OptionType::kInt,
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
};
std::unique_ptr<Configurable> 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<std::string, ConfigTestFactoryFunc> 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<std::unique_ptr<Configurable>>("SimpleUnique");
unique->reset(SimpleConfigurable::Create(
"Child",
TestConfigMode::kUniqueMode | TestConfigMode::kDefaultMode));
unique = unique->get()->GetOptions<std::unique_ptr<Configurable>>(
"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<std::string, std::string>> {
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<Configurable> object_;
};
void ConfigurableParamTest::TestConfigureOptions(
const ConfigOptions& config_options) {
std::unique_ptr<Configurable> base, copy;
std::unordered_set<std::string> 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<std::string, std::string> 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<std::string> names;
std::unique_ptr<Configurable> 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<std::string, std::string> 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<std::string, std::string>("Simple",
"int=42;bool=true;string=s"),
std::pair<std::string, std::string>(
"Mutable", "int=42;unique={int=33;string=unique}"),
std::pair<std::string, std::string>(
"Struct", "struct={int=33;bool=true;string=s;}"),
std::pair<std::string, std::string>("Shared",
"int=33;bool=true;string=outer;"
"shared={int=42;string=shared}"),
std::pair<std::string, std::string>("Unique",
"int=33;bool=true;string=outer;"
"unique={int=42;string=unique}"),
std::pair<std::string, std::string>("Nested",
"int=11;bool=true;string=outer;"
"pointer={int=22;string=pointer};"
"unique={int=33;string=unique};"
"shared={int=44;string=shared}"),
std::pair<std::string, std::string>("ThreeDeep",
"int=11;bool=true;string=outer;"
"unique={int=22;string=inner;"
"unique={int=33;string=unique}};"),
std::pair<std::string, std::string>("DBOptions",
"max_background_jobs=100;"
"max_open_files=200;"),
std::pair<std::string, std::string>("CFOptions",
"table_factory=BlockBasedTable;"
"disable_auto_compactions=true;"),
std::pair<std::string, std::string>("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();
}