1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9
10 #include <functional>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "db/arena_wrapped_db_iter.h"
16 #include "db/column_family.h"
17 #include "db/db_iter.h"
18 #include "db/db_test_util.h"
19 #include "db/dbformat.h"
20 #include "db/write_batch_internal.h"
21 #include "port/port.h"
22 #include "port/stack_trace.h"
23 #include "util/string_util.h"
24 #include "utilities/merge_operators.h"
25
26 namespace ROCKSDB_NAMESPACE {
27
28 // kTypeBlobIndex is a value type used by BlobDB only. The base rocksdb
29 // should accept the value type on write, and report not supported value
30 // for reads, unless caller request for it explicitly. The base rocksdb
31 // doesn't understand format of actual blob index (the value).
32 class DBBlobIndexTest : public DBTestBase {
33 public:
34 enum Tier {
35 kMemtable = 0,
36 kImmutableMemtables = 1,
37 kL0SstFile = 2,
38 kLnSstFile = 3,
39 };
40 const std::vector<Tier> kAllTiers = {Tier::kMemtable,
41 Tier::kImmutableMemtables,
42 Tier::kL0SstFile, Tier::kLnSstFile};
43
DBBlobIndexTest()44 DBBlobIndexTest() : DBTestBase("db_blob_index_test", /*env_do_fsync=*/true) {}
45
cfh()46 ColumnFamilyHandle* cfh() { return dbfull()->DefaultColumnFamily(); }
47
cfd()48 ColumnFamilyData* cfd() {
49 return static_cast_with_check<ColumnFamilyHandleImpl>(cfh())->cfd();
50 }
51
PutBlobIndex(WriteBatch * batch,const Slice & key,const Slice & blob_index)52 Status PutBlobIndex(WriteBatch* batch, const Slice& key,
53 const Slice& blob_index) {
54 return WriteBatchInternal::PutBlobIndex(batch, cfd()->GetID(), key,
55 blob_index);
56 }
57
Write(WriteBatch * batch)58 Status Write(WriteBatch* batch) {
59 return dbfull()->Write(WriteOptions(), batch);
60 }
61
GetImpl(const Slice & key,bool * is_blob_index=nullptr,const Snapshot * snapshot=nullptr)62 std::string GetImpl(const Slice& key, bool* is_blob_index = nullptr,
63 const Snapshot* snapshot = nullptr) {
64 ReadOptions read_options;
65 read_options.snapshot = snapshot;
66 PinnableSlice value;
67 DBImpl::GetImplOptions get_impl_options;
68 get_impl_options.column_family = cfh();
69 get_impl_options.value = &value;
70 get_impl_options.is_blob_index = is_blob_index;
71 auto s = dbfull()->GetImpl(read_options, key, get_impl_options);
72 if (s.IsNotFound()) {
73 return "NOT_FOUND";
74 }
75 if (s.IsCorruption()) {
76 return "CORRUPTION";
77 }
78 if (s.IsNotSupported()) {
79 return "NOT_SUPPORTED";
80 }
81 if (!s.ok()) {
82 return s.ToString();
83 }
84 return value.ToString();
85 }
86
GetBlobIndex(const Slice & key,const Snapshot * snapshot=nullptr)87 std::string GetBlobIndex(const Slice& key,
88 const Snapshot* snapshot = nullptr) {
89 bool is_blob_index = false;
90 std::string value = GetImpl(key, &is_blob_index, snapshot);
91 if (!is_blob_index) {
92 return "NOT_BLOB";
93 }
94 return value;
95 }
96
GetBlobIterator()97 ArenaWrappedDBIter* GetBlobIterator() {
98 return dbfull()->NewIteratorImpl(
99 ReadOptions(), cfd(), dbfull()->GetLatestSequenceNumber(),
100 nullptr /*read_callback*/, true /*expose_blob_index*/);
101 }
102
GetTestOptions()103 Options GetTestOptions() {
104 Options options;
105 options.env = CurrentOptions().env;
106 options.create_if_missing = true;
107 options.num_levels = 2;
108 options.disable_auto_compactions = true;
109 // Disable auto flushes.
110 options.max_write_buffer_number = 10;
111 options.min_write_buffer_number_to_merge = 10;
112 options.merge_operator = MergeOperators::CreateStringAppendOperator();
113 return options;
114 }
115
MoveDataTo(Tier tier)116 void MoveDataTo(Tier tier) {
117 switch (tier) {
118 case Tier::kMemtable:
119 break;
120 case Tier::kImmutableMemtables:
121 ASSERT_OK(dbfull()->TEST_SwitchMemtable());
122 break;
123 case Tier::kL0SstFile:
124 ASSERT_OK(Flush());
125 break;
126 case Tier::kLnSstFile:
127 ASSERT_OK(Flush());
128 ASSERT_OK(Put("a", "dummy"));
129 ASSERT_OK(Put("z", "dummy"));
130 ASSERT_OK(Flush());
131 ASSERT_OK(
132 dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
133 #ifndef ROCKSDB_LITE
134 ASSERT_EQ("0,1", FilesPerLevel());
135 #endif // !ROCKSDB_LITE
136 break;
137 }
138 }
139 };
140
141 // Should be able to write kTypeBlobIndex to memtables and SST files.
TEST_F(DBBlobIndexTest,Write)142 TEST_F(DBBlobIndexTest, Write) {
143 for (auto tier : kAllTiers) {
144 DestroyAndReopen(GetTestOptions());
145 for (int i = 1; i <= 5; i++) {
146 std::string index = ToString(i);
147 WriteBatch batch;
148 ASSERT_OK(PutBlobIndex(&batch, "key" + index, "blob" + index));
149 ASSERT_OK(Write(&batch));
150 }
151 MoveDataTo(tier);
152 for (int i = 1; i <= 5; i++) {
153 std::string index = ToString(i);
154 ASSERT_EQ("blob" + index, GetBlobIndex("key" + index));
155 }
156 }
157 }
158
159 // Note: the following test case pertains to the StackableDB-based BlobDB
160 // implementation. Get should be able to return blob index if is_blob_index is
161 // provided, otherwise it should return Status::NotSupported (when reading from
162 // memtable) or Status::Corruption (when reading from SST). Reading from SST
163 // returns Corruption because we can't differentiate between the application
164 // accidentally opening the base DB of a stacked BlobDB and actual corruption
165 // when using the integrated BlobDB.
TEST_F(DBBlobIndexTest,Get)166 TEST_F(DBBlobIndexTest, Get) {
167 for (auto tier : kAllTiers) {
168 DestroyAndReopen(GetTestOptions());
169 WriteBatch batch;
170 ASSERT_OK(batch.Put("key", "value"));
171 ASSERT_OK(PutBlobIndex(&batch, "blob_key", "blob_index"));
172 ASSERT_OK(Write(&batch));
173 MoveDataTo(tier);
174 // Verify normal value
175 bool is_blob_index = false;
176 PinnableSlice value;
177 ASSERT_EQ("value", Get("key"));
178 ASSERT_EQ("value", GetImpl("key"));
179 ASSERT_EQ("value", GetImpl("key", &is_blob_index));
180 ASSERT_FALSE(is_blob_index);
181 // Verify blob index
182 if (tier <= kImmutableMemtables) {
183 ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
184 ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
185 } else {
186 ASSERT_TRUE(Get("blob_key", &value).IsCorruption());
187 ASSERT_EQ("CORRUPTION", GetImpl("blob_key"));
188 }
189 ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
190 ASSERT_TRUE(is_blob_index);
191 }
192 }
193
194 // Note: the following test case pertains to the StackableDB-based BlobDB
195 // implementation. Get should NOT return Status::NotSupported/Status::Corruption
196 // if blob index is updated with a normal value. See the test case above for
197 // more details.
TEST_F(DBBlobIndexTest,Updated)198 TEST_F(DBBlobIndexTest, Updated) {
199 for (auto tier : kAllTiers) {
200 DestroyAndReopen(GetTestOptions());
201 WriteBatch batch;
202 for (int i = 0; i < 10; i++) {
203 ASSERT_OK(PutBlobIndex(&batch, "key" + ToString(i), "blob_index"));
204 }
205 ASSERT_OK(Write(&batch));
206 // Avoid blob values from being purged.
207 const Snapshot* snapshot = dbfull()->GetSnapshot();
208 ASSERT_OK(Put("key1", "new_value"));
209 ASSERT_OK(Merge("key2", "a"));
210 ASSERT_OK(Merge("key2", "b"));
211 ASSERT_OK(Merge("key2", "c"));
212 ASSERT_OK(Delete("key3"));
213 ASSERT_OK(SingleDelete("key4"));
214 ASSERT_OK(Delete("key5"));
215 ASSERT_OK(Merge("key5", "a"));
216 ASSERT_OK(Merge("key5", "b"));
217 ASSERT_OK(Merge("key5", "c"));
218 ASSERT_OK(dbfull()->DeleteRange(WriteOptions(), cfh(), "key6", "key9"));
219 MoveDataTo(tier);
220 for (int i = 0; i < 10; i++) {
221 ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
222 }
223 ASSERT_EQ("new_value", Get("key1"));
224 if (tier <= kImmutableMemtables) {
225 ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
226 } else {
227 ASSERT_EQ("CORRUPTION", GetImpl("key2"));
228 }
229 ASSERT_EQ("NOT_FOUND", Get("key3"));
230 ASSERT_EQ("NOT_FOUND", Get("key4"));
231 ASSERT_EQ("a,b,c", GetImpl("key5"));
232 for (int i = 6; i < 9; i++) {
233 ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
234 }
235 ASSERT_EQ("blob_index", GetBlobIndex("key9"));
236 dbfull()->ReleaseSnapshot(snapshot);
237 }
238 }
239
240 // Note: the following test case pertains to the StackableDB-based BlobDB
241 // implementation. When a blob iterator is used, it should set the
242 // expose_blob_index flag for the underlying DBIter, and retrieve/return the
243 // corresponding blob value. If a regular DBIter is created (i.e.
244 // expose_blob_index is not set), it should return Status::Corruption.
TEST_F(DBBlobIndexTest,Iterate)245 TEST_F(DBBlobIndexTest, Iterate) {
246 const std::vector<std::vector<ValueType>> data = {
247 /*00*/ {kTypeValue},
248 /*01*/ {kTypeBlobIndex},
249 /*02*/ {kTypeValue},
250 /*03*/ {kTypeBlobIndex, kTypeValue},
251 /*04*/ {kTypeValue},
252 /*05*/ {kTypeValue, kTypeBlobIndex},
253 /*06*/ {kTypeValue},
254 /*07*/ {kTypeDeletion, kTypeBlobIndex},
255 /*08*/ {kTypeValue},
256 /*09*/ {kTypeSingleDeletion, kTypeBlobIndex},
257 /*10*/ {kTypeValue},
258 /*11*/ {kTypeMerge, kTypeMerge, kTypeMerge, kTypeBlobIndex},
259 /*12*/ {kTypeValue},
260 /*13*/
261 {kTypeMerge, kTypeMerge, kTypeMerge, kTypeDeletion, kTypeBlobIndex},
262 /*14*/ {kTypeValue},
263 /*15*/ {kTypeBlobIndex},
264 /*16*/ {kTypeValue},
265 };
266
267 auto get_key = [](int index) {
268 char buf[20];
269 snprintf(buf, sizeof(buf), "%02d", index);
270 return "key" + std::string(buf);
271 };
272
273 auto get_value = [&](int index, int version) {
274 return get_key(index) + "_value" + ToString(version);
275 };
276
277 auto check_iterator = [&](Iterator* iterator, Status::Code expected_status,
278 const Slice& expected_value) {
279 ASSERT_EQ(expected_status, iterator->status().code());
280 if (expected_status == Status::kOk) {
281 ASSERT_TRUE(iterator->Valid());
282 ASSERT_EQ(expected_value, iterator->value());
283 } else {
284 ASSERT_FALSE(iterator->Valid());
285 }
286 };
287
288 auto create_normal_iterator = [&]() -> Iterator* {
289 return dbfull()->NewIterator(ReadOptions());
290 };
291
292 auto create_blob_iterator = [&]() -> Iterator* { return GetBlobIterator(); };
293
294 auto check_is_blob = [&](bool is_blob) {
295 return [is_blob](Iterator* iterator) {
296 ASSERT_EQ(is_blob,
297 reinterpret_cast<ArenaWrappedDBIter*>(iterator)->IsBlob());
298 };
299 };
300
301 auto verify = [&](int index, Status::Code expected_status,
302 const Slice& forward_value, const Slice& backward_value,
303 std::function<Iterator*()> create_iterator,
304 std::function<void(Iterator*)> extra_check = nullptr) {
305 // Seek
306 auto* iterator = create_iterator();
307 ASSERT_OK(iterator->status());
308 ASSERT_OK(iterator->Refresh());
309 iterator->Seek(get_key(index));
310 check_iterator(iterator, expected_status, forward_value);
311 if (extra_check) {
312 extra_check(iterator);
313 }
314 delete iterator;
315
316 // Next
317 iterator = create_iterator();
318 ASSERT_OK(iterator->Refresh());
319 iterator->Seek(get_key(index - 1));
320 ASSERT_TRUE(iterator->Valid());
321 ASSERT_OK(iterator->status());
322 iterator->Next();
323 check_iterator(iterator, expected_status, forward_value);
324 if (extra_check) {
325 extra_check(iterator);
326 }
327 delete iterator;
328
329 // SeekForPrev
330 iterator = create_iterator();
331 ASSERT_OK(iterator->status());
332 ASSERT_OK(iterator->Refresh());
333 iterator->SeekForPrev(get_key(index));
334 check_iterator(iterator, expected_status, backward_value);
335 if (extra_check) {
336 extra_check(iterator);
337 }
338 delete iterator;
339
340 // Prev
341 iterator = create_iterator();
342 iterator->Seek(get_key(index + 1));
343 ASSERT_TRUE(iterator->Valid());
344 ASSERT_OK(iterator->status());
345 iterator->Prev();
346 check_iterator(iterator, expected_status, backward_value);
347 if (extra_check) {
348 extra_check(iterator);
349 }
350 delete iterator;
351 };
352
353 for (auto tier : {Tier::kMemtable} /*kAllTiers*/) {
354 // Avoid values from being purged.
355 std::vector<const Snapshot*> snapshots;
356 DestroyAndReopen(GetTestOptions());
357
358 // fill data
359 for (int i = 0; i < static_cast<int>(data.size()); i++) {
360 for (int j = static_cast<int>(data[i].size()) - 1; j >= 0; j--) {
361 std::string key = get_key(i);
362 std::string value = get_value(i, j);
363 WriteBatch batch;
364 switch (data[i][j]) {
365 case kTypeValue:
366 ASSERT_OK(Put(key, value));
367 break;
368 case kTypeDeletion:
369 ASSERT_OK(Delete(key));
370 break;
371 case kTypeSingleDeletion:
372 ASSERT_OK(SingleDelete(key));
373 break;
374 case kTypeMerge:
375 ASSERT_OK(Merge(key, value));
376 break;
377 case kTypeBlobIndex:
378 ASSERT_OK(PutBlobIndex(&batch, key, value));
379 ASSERT_OK(Write(&batch));
380 break;
381 default:
382 FAIL();
383 };
384 }
385 snapshots.push_back(dbfull()->GetSnapshot());
386 }
387 ASSERT_OK(
388 dbfull()->DeleteRange(WriteOptions(), cfh(), get_key(15), get_key(16)));
389 snapshots.push_back(dbfull()->GetSnapshot());
390 MoveDataTo(tier);
391
392 // Normal iterator
393 verify(1, Status::kCorruption, "", "", create_normal_iterator);
394 verify(3, Status::kCorruption, "", "", create_normal_iterator);
395 verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
396 create_normal_iterator);
397 verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
398 create_normal_iterator);
399 verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
400 create_normal_iterator);
401 verify(11, Status::kCorruption, "", "", create_normal_iterator);
402 verify(13, Status::kOk,
403 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
404 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
405 create_normal_iterator);
406 verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
407 create_normal_iterator);
408
409 // Iterator with blob support
410 verify(1, Status::kOk, get_value(1, 0), get_value(1, 0),
411 create_blob_iterator, check_is_blob(true));
412 verify(3, Status::kOk, get_value(3, 0), get_value(3, 0),
413 create_blob_iterator, check_is_blob(true));
414 verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
415 create_blob_iterator, check_is_blob(false));
416 verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
417 create_blob_iterator, check_is_blob(false));
418 verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
419 create_blob_iterator, check_is_blob(false));
420 if (tier <= kImmutableMemtables) {
421 verify(11, Status::kNotSupported, "", "", create_blob_iterator);
422 } else {
423 verify(11, Status::kCorruption, "", "", create_blob_iterator);
424 }
425 verify(13, Status::kOk,
426 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
427 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
428 create_blob_iterator, check_is_blob(false));
429 verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
430 create_blob_iterator, check_is_blob(false));
431
432 #ifndef ROCKSDB_LITE
433 // Iterator with blob support and using seek.
434 ASSERT_OK(dbfull()->SetOptions(
435 cfh(), {{"max_sequential_skip_in_iterations", "0"}}));
436 verify(1, Status::kOk, get_value(1, 0), get_value(1, 0),
437 create_blob_iterator, check_is_blob(true));
438 verify(3, Status::kOk, get_value(3, 0), get_value(3, 0),
439 create_blob_iterator, check_is_blob(true));
440 verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
441 create_blob_iterator, check_is_blob(false));
442 verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
443 create_blob_iterator, check_is_blob(false));
444 verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
445 create_blob_iterator, check_is_blob(false));
446 if (tier <= kImmutableMemtables) {
447 verify(11, Status::kNotSupported, "", "", create_blob_iterator);
448 } else {
449 verify(11, Status::kCorruption, "", "", create_blob_iterator);
450 }
451 verify(13, Status::kOk,
452 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
453 get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
454 create_blob_iterator, check_is_blob(false));
455 verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
456 create_blob_iterator, check_is_blob(false));
457 #endif // !ROCKSDB_LITE
458
459 for (auto* snapshot : snapshots) {
460 dbfull()->ReleaseSnapshot(snapshot);
461 }
462 }
463 }
464
TEST_F(DBBlobIndexTest,IntegratedBlobIterate)465 TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
466 const std::vector<std::vector<std::string>> data = {
467 /*00*/ {"Put"},
468 /*01*/ {"Put", "Merge", "Merge", "Merge"},
469 /*02*/ {"Put"}};
470
471 auto get_key = [](size_t index) { return ("key" + std::to_string(index)); };
472
473 auto get_value = [&](size_t index, size_t version) {
474 return get_key(index) + "_value" + ToString(version);
475 };
476
477 auto check_iterator = [&](Iterator* iterator, Status expected_status,
478 const Slice& expected_value) {
479 ASSERT_EQ(expected_status, iterator->status());
480 if (expected_status.ok()) {
481 ASSERT_TRUE(iterator->Valid());
482 ASSERT_EQ(expected_value, iterator->value());
483 } else {
484 ASSERT_FALSE(iterator->Valid());
485 }
486 };
487
488 auto verify = [&](size_t index, Status expected_status,
489 const Slice& expected_value) {
490 // Seek
491 {
492 Iterator* iterator = db_->NewIterator(ReadOptions());
493 std::unique_ptr<Iterator> iterator_guard(iterator);
494 ASSERT_OK(iterator->status());
495 ASSERT_OK(iterator->Refresh());
496 iterator->Seek(get_key(index));
497 check_iterator(iterator, expected_status, expected_value);
498 }
499 // Next
500 {
501 Iterator* iterator = db_->NewIterator(ReadOptions());
502 std::unique_ptr<Iterator> iterator_guard(iterator);
503 ASSERT_OK(iterator->Refresh());
504 iterator->Seek(get_key(index - 1));
505 ASSERT_TRUE(iterator->Valid());
506 ASSERT_OK(iterator->status());
507 iterator->Next();
508 check_iterator(iterator, expected_status, expected_value);
509 }
510 // SeekForPrev
511 {
512 Iterator* iterator = db_->NewIterator(ReadOptions());
513 std::unique_ptr<Iterator> iterator_guard(iterator);
514 ASSERT_OK(iterator->status());
515 ASSERT_OK(iterator->Refresh());
516 iterator->SeekForPrev(get_key(index));
517 check_iterator(iterator, expected_status, expected_value);
518 }
519 // Prev
520 {
521 Iterator* iterator = db_->NewIterator(ReadOptions());
522 std::unique_ptr<Iterator> iterator_guard(iterator);
523 iterator->Seek(get_key(index + 1));
524 ASSERT_TRUE(iterator->Valid());
525 ASSERT_OK(iterator->status());
526 iterator->Prev();
527 check_iterator(iterator, expected_status, expected_value);
528 }
529 };
530
531 Options options = GetTestOptions();
532 options.enable_blob_files = true;
533 options.min_blob_size = 0;
534
535 DestroyAndReopen(options);
536
537 // fill data
538 for (size_t i = 0; i < data.size(); i++) {
539 for (size_t j = 0; j < data[i].size(); j++) {
540 std::string key = get_key(i);
541 std::string value = get_value(i, j);
542 if (data[i][j] == "Put") {
543 ASSERT_OK(Put(key, value));
544 ASSERT_OK(Flush());
545 } else if (data[i][j] == "Merge") {
546 ASSERT_OK(Merge(key, value));
547 ASSERT_OK(Flush());
548 }
549 }
550 }
551
552 std::string expected_value = get_value(1, 0) + "," + get_value(1, 1) + "," +
553 get_value(1, 2) + "," + get_value(1, 3);
554 Status expected_status;
555 verify(1, expected_status, expected_value);
556
557 #ifndef ROCKSDB_LITE
558 // Test DBIter::FindValueForCurrentKeyUsingSeek flow.
559 ASSERT_OK(dbfull()->SetOptions(cfh(),
560 {{"max_sequential_skip_in_iterations", "0"}}));
561 verify(1, expected_status, expected_value);
562 #endif // !ROCKSDB_LITE
563 }
564
565 } // namespace ROCKSDB_NAMESPACE
566
567 #ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
568 extern "C" {
569 void RegisterCustomObjects(int argc, char** argv);
570 }
571 #else
RegisterCustomObjects(int,char **)572 void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
573 #endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
574
main(int argc,char ** argv)575 int main(int argc, char** argv) {
576 ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
577 ::testing::InitGoogleTest(&argc, argv);
578 RegisterCustomObjects(argc, argv);
579 return RUN_ALL_TESTS();
580 }
581