/Users/deen/code/yugabyte-db/src/yb/rocksdb/utilities/options/options_util_test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under the BSD-style license found in the |
3 | | // LICENSE file in the root directory of this source tree. An additional grant |
4 | | // of patent rights can be found in the PATENTS file in the same directory. |
5 | | // |
6 | | // The following only applies to changes made to this file as part of YugaByte development. |
7 | | // |
8 | | // Portions Copyright (c) YugaByte, Inc. |
9 | | // |
10 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
11 | | // in compliance with the License. You may obtain a copy of the License at |
12 | | // |
13 | | // http://www.apache.org/licenses/LICENSE-2.0 |
14 | | // |
15 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
16 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
17 | | // or implied. See the License for the specific language governing permissions and limitations |
18 | | // under the License. |
19 | | // |
20 | | |
21 | | #ifndef ROCKSDB_LITE |
22 | | #ifndef __STDC_FORMAT_MACROS |
23 | | #define __STDC_FORMAT_MACROS |
24 | | #endif |
25 | | |
26 | | #include <inttypes.h> |
27 | | |
28 | | #include <cctype> |
29 | | #include <unordered_map> |
30 | | |
31 | | #include "yb/rocksdb/db.h" |
32 | | #include "yb/rocksdb/table.h" |
33 | | #include "yb/rocksdb/utilities/options_util.h" |
34 | | #include "yb/rocksdb/util/options_parser.h" |
35 | | #include "yb/rocksdb/util/random.h" |
36 | | #include "yb/rocksdb/util/testharness.h" |
37 | | #include "yb/rocksdb/util/testutil.h" |
38 | | |
39 | | #include "yb/util/test_util.h" |
40 | | |
41 | | #ifndef GFLAGS |
42 | | bool FLAGS_enable_print = false; |
43 | | #else |
44 | | #include <gflags/gflags.h> |
45 | | using GFLAGS::ParseCommandLineFlags; |
46 | | DEFINE_bool(enable_print, false, "Print options generated to console."); |
47 | | #endif // GFLAGS |
48 | | |
49 | | namespace rocksdb { |
50 | | class OptionsUtilTest : public RocksDBTest { |
51 | | public: |
52 | 3 | OptionsUtilTest() : rnd_(0xFB) { |
53 | 3 | env_.reset(new test::StringEnv(Env::Default())); |
54 | 3 | dbname_ = test::TmpDir() + "/options_util_test"; |
55 | 3 | } |
56 | | |
57 | | protected: |
58 | | std::unique_ptr<test::StringEnv> env_; |
59 | | std::string dbname_; |
60 | | Random rnd_; |
61 | | }; |
62 | | |
63 | 25 | bool IsBlockBasedTableFactory(TableFactory* tf) { |
64 | 25 | return tf->Name() == BlockBasedTableFactory().Name(); |
65 | 25 | } |
66 | | |
67 | 1 | TEST_F(OptionsUtilTest, SaveAndLoad) { |
68 | 1 | const size_t kCFCount = 5; |
69 | | |
70 | 1 | DBOptions db_opt; |
71 | 1 | std::vector<std::string> cf_names; |
72 | 1 | std::vector<ColumnFamilyOptions> cf_opts; |
73 | 1 | test::RandomInitDBOptions(&db_opt, &rnd_); |
74 | 6 | for (size_t i = 0; i < kCFCount; ++i) { |
75 | 1 | cf_names.push_back(i == 0 ? kDefaultColumnFamilyName |
76 | 4 | : test::RandomName(&rnd_, 10)); |
77 | 5 | cf_opts.emplace_back(); |
78 | 5 | test::RandomInitCFOptions(&cf_opts.back(), &rnd_); |
79 | 5 | } |
80 | | |
81 | 1 | const std::string kFileName = "OPTIONS-123456"; |
82 | 1 | ASSERT_OK(PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName, env_.get())); |
83 | | |
84 | 1 | DBOptions loaded_db_opt; |
85 | 1 | std::vector<ColumnFamilyDescriptor> loaded_cf_descs; |
86 | 1 | ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt, |
87 | 1 | &loaded_cf_descs)); |
88 | | |
89 | 1 | ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt)); |
90 | 1 | test::RandomInitDBOptions(&db_opt, &rnd_); |
91 | 1 | ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt)); |
92 | | |
93 | 6 | for (size_t i = 0; i < kCFCount; ++i) { |
94 | 5 | ASSERT_EQ(cf_names[i], loaded_cf_descs[i].name); |
95 | 5 | ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( |
96 | 5 | cf_opts[i], loaded_cf_descs[i].options)); |
97 | 5 | if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) { |
98 | 0 | ASSERT_OK(RocksDBOptionsParser::VerifyTableFactory( |
99 | 0 | cf_opts[i].table_factory.get(), |
100 | 0 | loaded_cf_descs[i].options.table_factory.get())); |
101 | 0 | } |
102 | 5 | test::RandomInitCFOptions(&cf_opts[i], &rnd_); |
103 | 5 | ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( |
104 | 5 | cf_opts[i], loaded_cf_descs[i].options)); |
105 | 5 | } |
106 | | |
107 | 6 | for (size_t i = 0; i < kCFCount; ++i) { |
108 | 5 | if (cf_opts[i].compaction_filter) { |
109 | 5 | delete cf_opts[i].compaction_filter; |
110 | 5 | } |
111 | 5 | } |
112 | 1 | } |
113 | | |
114 | 1 | TEST_F(OptionsUtilTest, CompareIncludeOptions) { |
115 | 1 | const size_t kCFCount = 5; |
116 | | |
117 | 1 | DBOptions db_opt; |
118 | 1 | std::vector<std::string> cf_names; |
119 | 1 | std::vector<ColumnFamilyOptions> cf_opts; |
120 | 1 | test::RandomInitDBOptions(&db_opt, &rnd_); |
121 | 6 | for (size_t i = 0; i < kCFCount; ++i) { |
122 | 1 | cf_names.push_back(i == 0 ? kDefaultColumnFamilyName |
123 | 4 | : test::RandomName(&rnd_, 10)); |
124 | 5 | cf_opts.emplace_back(); |
125 | 5 | test::RandomInitCFOptions(&cf_opts.back(), &rnd_); |
126 | 5 | } |
127 | | |
128 | 1 | std::tuple<IncludeHeader, IncludeFileVersion> include_opts[] { |
129 | 1 | std::make_tuple(IncludeHeader::kTrue, IncludeFileVersion::kTrue), |
130 | 1 | std::make_tuple(IncludeHeader::kTrue, IncludeFileVersion::kFalse), |
131 | 1 | std::make_tuple(IncludeHeader::kFalse, IncludeFileVersion::kTrue), |
132 | 1 | std::make_tuple(IncludeHeader::kFalse, IncludeFileVersion::kFalse) |
133 | 1 | }; |
134 | | |
135 | 1 | const std::string kFileName = "OPTIONS-123456"; |
136 | | |
137 | 4 | for (auto const& opts : include_opts) { |
138 | 4 | if (env_->FileExists(kFileName).ok()) { |
139 | 0 | ASSERT_OK(env_->DeleteFile(kFileName)); |
140 | 0 | } |
141 | 4 | ASSERT_OK(PersistRocksDBOptions( |
142 | 4 | db_opt, cf_names, cf_opts, kFileName, env_.get(), std::get<0>(opts), std::get<1>(opts))); |
143 | | |
144 | 4 | DBOptions loaded_db_opt; |
145 | 4 | std::vector<ColumnFamilyDescriptor> loaded_cf_descs; |
146 | 4 | ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt, &loaded_cf_descs)); |
147 | 4 | ASSERT_OK(env_->DeleteFile(kFileName)); |
148 | | |
149 | 4 | ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt)); |
150 | 4 | ASSERT_EQ(loaded_cf_descs.size(), cf_names.size()); |
151 | 4 | ASSERT_EQ(loaded_cf_descs.size(), cf_opts.size()); |
152 | 24 | for (size_t i = 0; i < loaded_cf_descs.size(); ++i) { |
153 | 20 | ASSERT_EQ(cf_names[i], loaded_cf_descs[i].name); |
154 | 20 | ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(cf_opts[i], loaded_cf_descs[i].options)); |
155 | 20 | if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) { |
156 | 0 | ASSERT_OK(RocksDBOptionsParser::VerifyTableFactory( |
157 | 0 | cf_opts[i].table_factory.get(), loaded_cf_descs[i].options.table_factory.get())); |
158 | 0 | } |
159 | 20 | } |
160 | 4 | } |
161 | | |
162 | 6 | for (size_t i = 0; i < cf_opts.size(); ++i) { |
163 | 5 | if (cf_opts[i].compaction_filter) { |
164 | 5 | delete cf_opts[i].compaction_filter; |
165 | 5 | } |
166 | 5 | } |
167 | 1 | } |
168 | | |
169 | | namespace { |
170 | | class DummyTableFactory : public TableFactory { |
171 | | public: |
172 | 1 | DummyTableFactory() {} |
173 | 1 | virtual ~DummyTableFactory() {} |
174 | | |
175 | 2 | const char* Name() const override { return "DummyTableFactory"; } |
176 | | |
177 | | virtual Status NewTableReader(const TableReaderOptions& table_reader_options, |
178 | | unique_ptr<RandomAccessFileReader>&& file, |
179 | | uint64_t file_size, |
180 | 0 | unique_ptr<TableReader>* table_reader) const override { |
181 | 0 | return STATUS(NotSupported, ""); |
182 | 0 | } |
183 | | |
184 | 0 | bool IsSplitSstForWriteSupported() const override { return false; } |
185 | | |
186 | | virtual TableBuilder* NewTableBuilder( |
187 | | const TableBuilderOptions& table_builder_options, uint32_t column_family_id, |
188 | 0 | WritableFileWriter* metadata_file, WritableFileWriter* data_file = nullptr) const override { |
189 | 0 | return nullptr; |
190 | 0 | } |
191 | | |
192 | | virtual Status SanitizeOptions(const DBOptions& db_opts, |
193 | 0 | const ColumnFamilyOptions& cf_opts) const override { |
194 | 0 | return STATUS(NotSupported, ""); |
195 | 0 | } |
196 | | |
197 | 0 | std::string GetPrintableTableOptions() const override { return ""; } |
198 | | }; |
199 | | |
200 | | class DummyMergeOperator : public MergeOperator { |
201 | | public: |
202 | 1 | DummyMergeOperator() {} |
203 | 1 | virtual ~DummyMergeOperator() {} |
204 | | |
205 | | virtual bool FullMerge(const Slice& key, const Slice* existing_value, |
206 | | const std::deque<std::string>& operand_list, |
207 | 0 | std::string* new_value, Logger* logger) const { |
208 | 0 | return false; |
209 | 0 | } |
210 | | |
211 | | virtual bool PartialMergeMulti(const Slice& key, |
212 | | const std::deque<Slice>& operand_list, |
213 | 0 | std::string* new_value, Logger* logger) const { |
214 | 0 | return false; |
215 | 0 | } |
216 | | |
217 | 2 | virtual const char* Name() const { return "DummyMergeOperator"; } |
218 | | }; |
219 | | |
220 | | class DummySliceTransform : public SliceTransform { |
221 | | public: |
222 | 2 | DummySliceTransform() {} |
223 | 2 | virtual ~DummySliceTransform() {} |
224 | | |
225 | | // Return the name of this transformation. |
226 | 3 | virtual const char* Name() const { return "DummySliceTransform"; } |
227 | | |
228 | | // transform a src in domain to a dst in the range |
229 | 0 | virtual Slice Transform(const Slice& src) const { return src; } |
230 | | |
231 | | // determine whether this is a valid src upon the function applies |
232 | 0 | virtual bool InDomain(const Slice& src) const { return false; } |
233 | | |
234 | | // determine whether dst=Transform(src) for some src |
235 | 0 | virtual bool InRange(const Slice& dst) const { return false; } |
236 | | }; |
237 | | |
238 | | } // namespace |
239 | | |
240 | 1 | TEST_F(OptionsUtilTest, SanityCheck) { |
241 | 1 | DBOptions db_opt; |
242 | 1 | std::vector<ColumnFamilyDescriptor> cf_descs; |
243 | 1 | const size_t kCFCount = 5; |
244 | 6 | for (size_t i = 0; i < kCFCount; ++i) { |
245 | 5 | cf_descs.emplace_back(); |
246 | 5 | cf_descs.back().name = |
247 | 4 | (i == 0) ? kDefaultColumnFamilyName : test::RandomName(&rnd_, 10); |
248 | | |
249 | 5 | cf_descs.back().options.table_factory.reset(NewBlockBasedTableFactory()); |
250 | | // Assign non-null values to prefix_extractors except the first cf. |
251 | 5 | cf_descs.back().options.prefix_extractor.reset( |
252 | 4 | i != 0 ? test::RandomSliceTransform(&rnd_) : nullptr); |
253 | 5 | cf_descs.back().options.merge_operator.reset( |
254 | 5 | test::RandomMergeOperator(&rnd_)); |
255 | 5 | } |
256 | | |
257 | 1 | db_opt.create_missing_column_families = true; |
258 | 1 | db_opt.create_if_missing = true; |
259 | | |
260 | 1 | ASSERT_OK(DestroyDB(dbname_, Options(db_opt, cf_descs[0].options))); |
261 | 1 | DB* db; |
262 | 1 | std::vector<ColumnFamilyHandle*> handles; |
263 | | // open and persist the options |
264 | 1 | ASSERT_OK(DB::Open(db_opt, dbname_, cf_descs, &handles, &db)); |
265 | | |
266 | | // close the db |
267 | 5 | for (auto* handle : handles) { |
268 | 5 | delete handle; |
269 | 5 | } |
270 | 1 | delete db; |
271 | | |
272 | | // perform sanity check |
273 | 1 | ASSERT_OK( |
274 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
275 | | |
276 | 1 | ASSERT_GE(kCFCount, 5); |
277 | | // merge operator |
278 | 1 | { |
279 | 1 | std::shared_ptr<MergeOperator> merge_op = |
280 | 1 | cf_descs[0].options.merge_operator; |
281 | | |
282 | 1 | ASSERT_NE(merge_op.get(), nullptr); |
283 | 1 | cf_descs[0].options.merge_operator.reset(); |
284 | 1 | ASSERT_NOK( |
285 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
286 | | |
287 | 1 | cf_descs[0].options.merge_operator.reset(new DummyMergeOperator()); |
288 | 1 | ASSERT_NOK( |
289 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
290 | | |
291 | 1 | cf_descs[0].options.merge_operator = merge_op; |
292 | 1 | ASSERT_OK( |
293 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
294 | 1 | } |
295 | | |
296 | | // prefix extractor |
297 | 1 | { |
298 | 1 | std::shared_ptr<const SliceTransform> prefix_extractor = |
299 | 1 | cf_descs[1].options.prefix_extractor; |
300 | | |
301 | | // It's okay to set prefix_extractor to nullptr. |
302 | 1 | ASSERT_NE(prefix_extractor, nullptr); |
303 | 1 | cf_descs[1].options.prefix_extractor.reset(); |
304 | 1 | ASSERT_OK( |
305 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
306 | | |
307 | 1 | cf_descs[1].options.prefix_extractor.reset(new DummySliceTransform()); |
308 | 1 | ASSERT_NOK( |
309 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
310 | | |
311 | 1 | cf_descs[1].options.prefix_extractor = prefix_extractor; |
312 | 1 | ASSERT_OK( |
313 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
314 | 1 | } |
315 | | |
316 | | // prefix extractor nullptr case |
317 | 1 | { |
318 | 1 | std::shared_ptr<const SliceTransform> prefix_extractor = |
319 | 1 | cf_descs[0].options.prefix_extractor; |
320 | | |
321 | | // It's okay to set prefix_extractor to nullptr. |
322 | 1 | ASSERT_EQ(prefix_extractor, nullptr); |
323 | 1 | cf_descs[0].options.prefix_extractor.reset(); |
324 | 1 | ASSERT_OK( |
325 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
326 | | |
327 | | // It's okay to change prefix_extractor from nullptr to non-nullptr |
328 | 1 | cf_descs[0].options.prefix_extractor.reset(new DummySliceTransform()); |
329 | 1 | ASSERT_OK( |
330 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
331 | | |
332 | 1 | cf_descs[0].options.prefix_extractor = prefix_extractor; |
333 | 1 | ASSERT_OK( |
334 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
335 | 1 | } |
336 | | |
337 | | // comparator |
338 | 1 | { |
339 | 1 | test::SimpleSuffixReverseComparator comparator; |
340 | | |
341 | 1 | auto* prev_comparator = cf_descs[2].options.comparator; |
342 | 1 | cf_descs[2].options.comparator = &comparator; |
343 | 1 | ASSERT_NOK( |
344 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
345 | | |
346 | 1 | cf_descs[2].options.comparator = prev_comparator; |
347 | 1 | ASSERT_OK( |
348 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
349 | 1 | } |
350 | | |
351 | | // table factory |
352 | 1 | { |
353 | 1 | std::shared_ptr<TableFactory> table_factory = |
354 | 1 | cf_descs[3].options.table_factory; |
355 | | |
356 | 1 | ASSERT_NE(table_factory, nullptr); |
357 | 1 | cf_descs[3].options.table_factory.reset(new DummyTableFactory()); |
358 | 1 | ASSERT_NOK( |
359 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
360 | | |
361 | 1 | cf_descs[3].options.table_factory = table_factory; |
362 | 1 | ASSERT_OK( |
363 | 1 | CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs)); |
364 | 1 | } |
365 | 1 | } |
366 | | |
367 | | } // namespace rocksdb |
368 | | |
369 | 13.2k | int main(int argc, char** argv) { |
370 | 13.2k | ::testing::InitGoogleTest(&argc, argv); |
371 | 13.2k | #ifdef GFLAGS |
372 | 13.2k | ParseCommandLineFlags(&argc, &argv, true); |
373 | 13.2k | #endif // GFLAGS |
374 | 13.2k | return RUN_ALL_TESTS(); |
375 | 13.2k | } |
376 | | |
377 | | #else |
378 | | #include <cstdio> |
379 | | |
380 | | int main(int argc, char** argv) { |
381 | | printf("Skipped in RocksDBLite as utilities are not supported.\n"); |
382 | | return 0; |
383 | | } |
384 | | #endif // !ROCKSDB_LITE |