rocksdb/options/customizable_test.cc
mrambacher 41c4b665f4 Fix PrepareOptions for Customizable Classes (#8468)
Summary:
Added the Customizable::ConfigureNewObject method.  The method will configure the object if options are found and invoke PrepareOptions if the flag is set properly.

Added tests to test that PrepareOptions is properly called and to test if PrepareOptions fails.

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

Reviewed By: zhichao-cao

Differential Revision: D29494703

Pulled By: mrambacher

fbshipit-source-id: d5767dee5d7a98620ac66190262101cd0aa9d2b7
2021-06-30 14:09:36 -07:00

977 lines
36 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 "rocksdb/customizable.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/convenience.h"
#include "rocksdb/utilities/customizable_util.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_type.h"
#include "table/mock_table.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 {
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"; }
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);
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 {
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("A", &opts_, &a_option_info);
}
std::string GetId() const override { return id_; }
static const char* kClassName() { return "A"; }
private:
AOptions opts_;
const std::string id_;
};
#ifndef ROCKSDB_LITE
static int A_count = 0;
const FactoryFunc<TestCustomizable>& a_func =
ObjectLibrary::Default()->Register<TestCustomizable>(
"A.*",
[](const std::string& name, std::unique_ptr<TestCustomizable>* guard,
std::string* /* msg */) {
guard->reset(new ACustomizable(name));
A_count++;
return guard->get();
});
#endif // ROCKSDB_LITE
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_;
};
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;
}
}
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);
}
#ifndef ROCKSDB_LITE
const FactoryFunc<TestCustomizable>& s_func =
ObjectLibrary::Default()->Register<TestCustomizable>(
"S", [](const std::string& name,
std::unique_ptr<TestCustomizable>* /* guard */,
std::string* /* msg */) { return new BCustomizable(name); });
#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::kNone)},
{"shared", OptionTypeInfo::AsCustomSharedPtr<TestCustomizable>(
offsetof(struct SimpleOptions, cs),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
{"pointer", OptionTypeInfo::AsCustomRawPtr<TestCustomizable>(
offsetof(struct SimpleOptions, cp),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
#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);
}
};
class CustomizableTest : public testing::Test {
public:
CustomizableTest() { config_options_.invoke_prepare_options = false; }
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()->Register<TestCustomizable>(
"TEST.*",
[](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));
}
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;
}
}
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;
auto registry = ObjectRegistry::NewInstance();
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}"));
}
// 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");
ASSERT_TRUE(tc->IsInstanceOf("A"));
ASSERT_TRUE(tc->IsInstanceOf("TestCustomizable"));
ASSERT_FALSE(tc->IsInstanceOf("B"));
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()->Register<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_FALSE(base->IsPrepared());
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);
ASSERT_TRUE(base->IsPrepared());
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_TRUE(simple->cs->IsPrepared());
ASSERT_TRUE(simple->cp->IsPrepared());
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_FALSE(base->IsPrepared());
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_FALSE(simple->cs->IsPrepared());
ASSERT_FALSE(simple->cp->IsPrepared());
ASSERT_OK(base->PrepareOptions(config_options_));
ASSERT_FALSE(base->IsPrepared());
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_FALSE(simple->cs->IsPrepared());
ASSERT_FALSE(simple->cp->IsPrepared());
ASSERT_OK(base->PrepareOptions(prepared));
ASSERT_TRUE(base->IsPrepared());
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_TRUE(simple->cs->IsPrepared());
ASSERT_TRUE(simple->cp->IsPrepared());
delete simple->cp;
base.reset(new SimpleConfigurable());
ASSERT_NOK(
base->ConfigureFromString(prepared, "unique={id=P; can_prepare=false}"));
simple = base->GetOptions<SimpleOptions>();
ASSERT_NE(simple, nullptr);
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_OK(
base->ConfigureFromString(prepared, "unique={id=P; can_prepare=true}"));
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=P; can_prepare=true}"));
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_OK(simple->cu->PrepareOptions(prepared));
ASSERT_TRUE(simple->cu->IsPrepared());
ASSERT_OK(base->ConfigureFromString(config_options_,
"unique={id=P; can_prepare=false}"));
ASSERT_NE(simple->cu, nullptr);
ASSERT_FALSE(simple->cu->IsPrepared());
ASSERT_NOK(simple->cu->PrepareOptions(prepared));
ASSERT_FALSE(simple->cu->IsPrepared());
}
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
};
class InnerCustomizable : public Customizable {
public:
explicit InnerCustomizable(const std::shared_ptr<Customizable>& w)
: inner_(w) {}
static const char* kClassName() { return "Inner"; }
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 inner_.get(); }
private:
std::shared_ptr<Customizable> inner_;
};
class WrappedCustomizable1 : public InnerCustomizable {
public:
explicit WrappedCustomizable1(const std::shared_ptr<Customizable>& w)
: InnerCustomizable(w) {}
const char* Name() const override { return kClassName(); }
static const char* kClassName() { return "Wrapped1"; }
};
class WrappedCustomizable2 : public InnerCustomizable {
public:
explicit WrappedCustomizable2(const std::shared_ptr<Customizable>& w)
: InnerCustomizable(w) {}
const char* Name() const override { return kClassName(); }
static const char* kClassName() { return "Wrapped2"; }
};
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());
}
class ShallowCustomizable : public Customizable {
public:
ShallowCustomizable() {
inner_ = std::make_shared<ACustomizable>("a");
RegisterOptions("inner", &inner_, &inner_option_info);
};
static const char* kClassName() { return "shallow"; }
const char* Name() const override { return kClassName(); }
private:
std::shared_ptr<TestCustomizable> inner_;
};
TEST_F(CustomizableTest, TestStringDepth) {
ConfigOptions shallow = config_options_;
std::unique_ptr<Configurable> c(new ShallowCustomizable());
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, NewCustomizableTest) {
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_EQ(A_count, 3);
}
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, 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;
ConfigOptions options = config_options_;
ASSERT_FALSE(mc.IsPrepared());
ASSERT_OK(mc.ConfigureOption(options, "mutable", "{id=B;}"));
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.ConfigureOption(options, "immutable", "{id=B;}"));
options.mutable_options_only = true;
std::string opt_str;
ASSERT_OK(mc.GetOptionString(options, &opt_str));
MutableCustomizable mc2;
ASSERT_OK(mc2.ConfigureFromString(options, opt_str));
std::string mismatch;
ASSERT_TRUE(mc.AreEquivalent(options, &mc2, &mismatch));
options.mutable_options_only = false;
ASSERT_FALSE(mc.AreEquivalent(options, &mc2, &mismatch));
ASSERT_EQ(mismatch, "immutable");
}
#endif // !ROCKSDB_LITE
#ifndef ROCKSDB_LITE
// This method loads existing test classes into the ObjectRegistry
static int RegisterTestObjects(ObjectLibrary& library,
const std::string& /*arg*/) {
size_t num_types;
library.Register<TableFactory>(
"MockTable",
[](const std::string& /*uri*/, std::unique_ptr<TableFactory>* guard,
std::string* /* errmsg */) {
guard->reset(new mock::MockTableFactory());
return guard->get();
});
library.Register<const Comparator>(
test::SimpleSuffixReverseComparator::kClassName(),
[](const std::string& /*uri*/,
std::unique_ptr<const Comparator>* /*guard*/,
std::string* /* errmsg */) {
static test::SimpleSuffixReverseComparator ssrc;
return &ssrc;
});
return static_cast<int>(library.GetFactoryCount(&num_types));
}
static int RegisterLocalObjects(ObjectLibrary& library,
const std::string& /*arg*/) {
size_t num_types;
// Load any locally defined objects here
return static_cast<int>(library.GetFactoryCount(&num_types));
}
#endif // !ROCKSDB_LITE
class LoadCustomizableTest : public testing::Test {
public:
LoadCustomizableTest() { config_options_.ignore_unsupported_options = false; }
bool RegisterTests(const std::string& arg) {
#ifndef ROCKSDB_LITE
config_options_.registry->AddLibrary("custom-tests", RegisterTestObjects,
arg);
config_options_.registry->AddLibrary("local-tests", RegisterLocalObjects,
arg);
return true;
#else
(void)arg;
return false;
#endif // !ROCKSDB_LITE
}
protected:
DBOptions db_opts_;
ColumnFamilyOptions cf_opts_;
ConfigOptions config_options_;
};
TEST_F(LoadCustomizableTest, LoadTableFactoryTest) {
std::shared_ptr<TableFactory> factory;
ASSERT_NOK(
TableFactory::CreateFromString(config_options_, "MockTable", &factory));
ASSERT_OK(TableFactory::CreateFromString(
config_options_, TableFactory::kBlockBasedTableName(), &factory));
ASSERT_NE(factory, nullptr);
ASSERT_STREQ(factory->Name(), TableFactory::kBlockBasedTableName());
if (RegisterTests("Test")) {
ASSERT_OK(
TableFactory::CreateFromString(config_options_, "MockTable", &factory));
ASSERT_NE(factory, nullptr);
ASSERT_STREQ(factory->Name(), "MockTable");
}
}
TEST_F(LoadCustomizableTest, LoadComparatorTest) {
const Comparator* bytewise = BytewiseComparator();
const Comparator* reverse = ReverseBytewiseComparator();
const Comparator* result = nullptr;
ASSERT_NOK(Comparator::CreateFromString(
config_options_, test::SimpleSuffixReverseComparator::kClassName(),
&result));
ASSERT_OK(
Comparator::CreateFromString(config_options_, bytewise->Name(), &result));
ASSERT_EQ(result, bytewise);
ASSERT_OK(
Comparator::CreateFromString(config_options_, reverse->Name(), &result));
ASSERT_EQ(result, reverse);
if (RegisterTests("Test")) {
ASSERT_OK(Comparator::CreateFromString(
config_options_, test::SimpleSuffixReverseComparator::kClassName(),
&result));
ASSERT_NE(result, nullptr);
ASSERT_STREQ(result->Name(),
test::SimpleSuffixReverseComparator::kClassName());
}
}
} // 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();
}