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