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 #ifndef ROCKSDB_LITE
7 
8 #include "rocksdb/utilities/ldb_cmd.h"
9 #include "db/version_edit.h"
10 #include "db/version_set.h"
11 #include "env/composite_env_wrapper.h"
12 #include "file/filename.h"
13 #include "port/stack_trace.h"
14 #include "rocksdb/file_checksum.h"
15 #include "test_util/sync_point.h"
16 #include "test_util/testharness.h"
17 #include "test_util/testutil.h"
18 #include "util/file_checksum_helper.h"
19 
20 using std::string;
21 using std::vector;
22 using std::map;
23 
24 namespace ROCKSDB_NAMESPACE {
25 
26 class LdbCmdTest : public testing::Test {
27  public:
LdbCmdTest()28   LdbCmdTest() : testing::Test() {}
29 
TryLoadCustomOrDefaultEnv()30   Env* TryLoadCustomOrDefaultEnv() {
31     const char* test_env_uri = getenv("TEST_ENV_URI");
32     if (!test_env_uri) {
33       return Env::Default();
34     }
35     Env* env = Env::Default();
36     Env::LoadEnv(test_env_uri, &env, &env_guard_);
37     return env;
38   }
39 
40  private:
41   std::shared_ptr<Env> env_guard_;
42 };
43 
TEST_F(LdbCmdTest,HexToString)44 TEST_F(LdbCmdTest, HexToString) {
45   // map input to expected outputs.
46   // odd number of "hex" half bytes doesn't make sense
47   map<string, vector<int>> inputMap = {
48       {"0x07", {7}},        {"0x5050", {80, 80}},          {"0xFF", {-1}},
49       {"0x1234", {18, 52}}, {"0xaaAbAC", {-86, -85, -84}}, {"0x1203", {18, 3}},
50   };
51 
52   for (const auto& inPair : inputMap) {
53     auto actual = ROCKSDB_NAMESPACE::LDBCommand::HexToString(inPair.first);
54     auto expected = inPair.second;
55     for (unsigned int i = 0; i < actual.length(); i++) {
56       EXPECT_EQ(expected[i], static_cast<int>((signed char) actual[i]));
57     }
58     auto reverse = ROCKSDB_NAMESPACE::LDBCommand::StringToHex(actual);
59     EXPECT_STRCASEEQ(inPair.first.c_str(), reverse.c_str());
60   }
61 }
62 
TEST_F(LdbCmdTest,HexToStringBadInputs)63 TEST_F(LdbCmdTest, HexToStringBadInputs) {
64   const vector<string> badInputs = {
65       "0xZZ", "123", "0xx5", "0x111G", "0x123", "Ox12", "0xT", "0x1Q1",
66   };
67   for (const auto badInput : badInputs) {
68     try {
69       ROCKSDB_NAMESPACE::LDBCommand::HexToString(badInput);
70       std::cerr << "Should fail on bad hex value: " << badInput << "\n";
71       FAIL();
72     } catch (...) {
73     }
74   }
75 }
76 
TEST_F(LdbCmdTest,MemEnv)77 TEST_F(LdbCmdTest, MemEnv) {
78   Env* base_env = TryLoadCustomOrDefaultEnv();
79   std::unique_ptr<Env> env(NewMemEnv(base_env));
80   Options opts;
81   opts.env = env.get();
82   opts.create_if_missing = true;
83 
84   DB* db = nullptr;
85   std::string dbname = test::TmpDir();
86   ASSERT_OK(DB::Open(opts, dbname, &db));
87 
88   WriteOptions wopts;
89   for (int i = 0; i < 100; i++) {
90     char buf[16];
91     snprintf(buf, sizeof(buf), "%08d", i);
92     ASSERT_OK(db->Put(wopts, buf, buf));
93   }
94   FlushOptions fopts;
95   fopts.wait = true;
96   ASSERT_OK(db->Flush(fopts));
97 
98   delete db;
99 
100   char arg1[] = "./ldb";
101   char arg2[1024];
102   snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
103   char arg3[] = "dump_live_files";
104   char* argv[] = {arg1, arg2, arg3};
105 
106   ASSERT_EQ(0,
107             LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
108 }
109 
110 class FileChecksumTestHelper {
111  private:
112   Options options_;
113   DB* db_;
114   std::string dbname_;
115 
VerifyChecksum(LiveFileMetaData & file_meta)116   Status VerifyChecksum(LiveFileMetaData& file_meta) {
117     std::string cur_checksum;
118     std::string checksum_func_name;
119 
120     Status s;
121     EnvOptions soptions;
122     std::unique_ptr<SequentialFile> file_reader;
123     std::string file_path = dbname_ + "/" + file_meta.name;
124     s = options_.env->NewSequentialFile(file_path, &file_reader, soptions);
125     if (!s.ok()) {
126       return s;
127     }
128     std::unique_ptr<char[]> scratch(new char[2048]);
129     Slice result;
130     FileChecksumGenFactory* file_checksum_gen_factory =
131         options_.file_checksum_gen_factory.get();
132     if (file_checksum_gen_factory == nullptr) {
133       cur_checksum = kUnknownFileChecksum;
134       checksum_func_name = kUnknownFileChecksumFuncName;
135     } else {
136       FileChecksumGenContext gen_context;
137       gen_context.file_name = file_meta.name;
138       std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
139           file_checksum_gen_factory->CreateFileChecksumGenerator(gen_context);
140       checksum_func_name = file_checksum_gen->Name();
141       s = file_reader->Read(2048, &result, scratch.get());
142       if (!s.ok()) {
143         return s;
144       }
145       while (result.size() != 0) {
146         file_checksum_gen->Update(scratch.get(), result.size());
147         s = file_reader->Read(2048, &result, scratch.get());
148         if (!s.ok()) {
149           return s;
150         }
151       }
152       file_checksum_gen->Finalize();
153       cur_checksum = file_checksum_gen->GetChecksum();
154     }
155 
156     std::string stored_checksum = file_meta.file_checksum;
157     std::string stored_checksum_func_name = file_meta.file_checksum_func_name;
158     if ((cur_checksum != stored_checksum) ||
159         (checksum_func_name != stored_checksum_func_name)) {
160       return Status::Corruption(
161           "Checksum does not match! The file: " + file_meta.name +
162           ", checksum name: " + stored_checksum_func_name + " and checksum " +
163           stored_checksum + ". However, expected checksum name: " +
164           checksum_func_name + " and checksum " + cur_checksum);
165     }
166     return Status::OK();
167   }
168 
169  public:
FileChecksumTestHelper(Options & options,DB * db,std::string db_name)170   FileChecksumTestHelper(Options& options, DB* db, std::string db_name)
171       : options_(options), db_(db), dbname_(db_name) {}
~FileChecksumTestHelper()172   ~FileChecksumTestHelper() {}
173 
174   // Verify the checksum information in Manifest.
VerifyChecksumInManifest(const std::vector<LiveFileMetaData> & live_files)175   Status VerifyChecksumInManifest(
176       const std::vector<LiveFileMetaData>& live_files) {
177     // Step 1: verify if the dbname_ is correct
178     if (dbname_[dbname_.length() - 1] != '/') {
179       dbname_.append("/");
180     }
181 
182     // Step 2, get the the checksum information by recovering the VersionSet
183     // from Manifest.
184     std::unique_ptr<FileChecksumList> checksum_list(NewFileChecksumList());
185     EnvOptions sopt;
186     std::shared_ptr<Cache> tc(NewLRUCache(options_.max_open_files - 10,
187                                           options_.table_cache_numshardbits));
188     options_.db_paths.emplace_back(dbname_, 0);
189     options_.num_levels = 64;
190     WriteController wc(options_.delayed_write_rate);
191     WriteBufferManager wb(options_.db_write_buffer_size);
192     ImmutableDBOptions immutable_db_options(options_);
193     VersionSet versions(dbname_, &immutable_db_options, sopt, tc.get(), &wb,
194                         &wc, nullptr);
195     std::vector<std::string> cf_name_list;
196     Status s;
197     s = versions.ListColumnFamilies(&cf_name_list, dbname_,
198                                     immutable_db_options.fs.get());
199     if (s.ok()) {
200       std::vector<ColumnFamilyDescriptor> cf_list;
201       for (const auto& name : cf_name_list) {
202         fprintf(stdout, "cf_name: %s", name.c_str());
203         cf_list.emplace_back(name, ColumnFamilyOptions(options_));
204       }
205       s = versions.Recover(cf_list, true);
206     }
207     if (s.ok()) {
208       s = versions.GetLiveFilesChecksumInfo(checksum_list.get());
209     }
210     if (!s.ok()) {
211       return s;
212     }
213 
214     // Step 3 verify the checksum
215     if (live_files.size() != checksum_list->size()) {
216       return Status::Corruption("The number of files does not match!");
217     }
218     for (size_t i = 0; i < live_files.size(); i++) {
219       std::string stored_checksum = "";
220       std::string stored_func_name = "";
221       s = checksum_list->SearchOneFileChecksum(
222           live_files[i].file_number, &stored_checksum, &stored_func_name);
223       if (s.IsNotFound()) {
224         return s;
225       }
226       if (live_files[i].file_checksum != stored_checksum ||
227           live_files[i].file_checksum_func_name != stored_func_name) {
228         return Status::Corruption(
229             "Checksum does not match! The file: " +
230             ToString(live_files[i].file_number) +
231             ". In Manifest, checksum name: " + stored_func_name +
232             " and checksum " + stored_checksum +
233             ". However, expected checksum name: " +
234             live_files[i].file_checksum_func_name + " and checksum " +
235             live_files[i].file_checksum);
236       }
237     }
238     return Status::OK();
239   }
240 
241   // Verify the checksum of each file by recalculting the checksum and
242   // comparing it with the one being generated when a SST file is created.
VerifyEachFileChecksum()243   Status VerifyEachFileChecksum() {
244     assert(db_ != nullptr);
245     std::vector<LiveFileMetaData> live_files;
246     db_->GetLiveFilesMetaData(&live_files);
247     for (auto a_file : live_files) {
248       Status cs = VerifyChecksum(a_file);
249       if (!cs.ok()) {
250         return cs;
251       }
252     }
253     return Status::OK();
254   }
255 };
256 
TEST_F(LdbCmdTest,DumpFileChecksumNoChecksum)257 TEST_F(LdbCmdTest, DumpFileChecksumNoChecksum) {
258   Env* base_env = TryLoadCustomOrDefaultEnv();
259   std::unique_ptr<Env> env(NewMemEnv(base_env));
260   Options opts;
261   opts.env = env.get();
262   opts.create_if_missing = true;
263 
264   DB* db = nullptr;
265   std::string dbname = test::TmpDir();
266   ASSERT_OK(DB::Open(opts, dbname, &db));
267 
268   WriteOptions wopts;
269   FlushOptions fopts;
270   fopts.wait = true;
271   Random rnd(test::RandomSeed());
272   for (int i = 0; i < 200; i++) {
273     char buf[16];
274     snprintf(buf, sizeof(buf), "%08d", i);
275     std::string v;
276     test::RandomString(&rnd, 100, &v);
277     ASSERT_OK(db->Put(wopts, buf, v));
278   }
279   ASSERT_OK(db->Flush(fopts));
280   for (int i = 100; i < 300; i++) {
281     char buf[16];
282     snprintf(buf, sizeof(buf), "%08d", i);
283     std::string v;
284     test::RandomString(&rnd, 100, &v);
285     ASSERT_OK(db->Put(wopts, buf, v));
286   }
287   ASSERT_OK(db->Flush(fopts));
288   for (int i = 200; i < 400; i++) {
289     char buf[16];
290     snprintf(buf, sizeof(buf), "%08d", i);
291     std::string v;
292     test::RandomString(&rnd, 100, &v);
293     ASSERT_OK(db->Put(wopts, buf, v));
294   }
295   ASSERT_OK(db->Flush(fopts));
296   for (int i = 300; i < 400; i++) {
297     char buf[16];
298     snprintf(buf, sizeof(buf), "%08d", i);
299     std::string v;
300     test::RandomString(&rnd, 100, &v);
301     ASSERT_OK(db->Put(wopts, buf, v));
302   }
303   ASSERT_OK(db->Flush(fopts));
304 
305   char arg1[] = "./ldb";
306   char arg2[1024];
307   snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
308   char arg3[] = "file_checksum_dump";
309   char* argv[] = {arg1, arg2, arg3};
310 
311   ASSERT_EQ(0,
312             LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
313 
314   // Verify each sst file checksum value and checksum name
315   FileChecksumTestHelper fct_helper(opts, db, dbname);
316   ASSERT_OK(fct_helper.VerifyEachFileChecksum());
317 
318   // Manually trigger compaction
319   char b_buf[16];
320   snprintf(b_buf, sizeof(b_buf), "%08d", 0);
321   char e_buf[16];
322   snprintf(e_buf, sizeof(e_buf), "%08d", 399);
323   Slice begin(b_buf);
324   Slice end(e_buf);
325   CompactRangeOptions options;
326   ASSERT_OK(db->CompactRange(options, &begin, &end));
327   // Verify each sst file checksum after compaction
328   FileChecksumTestHelper fct_helper_ac(opts, db, dbname);
329   ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum());
330 
331   ASSERT_EQ(0,
332             LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
333 
334   // Verify the checksum information in memory is the same as that in Manifest;
335   std::vector<LiveFileMetaData> live_files;
336   db->GetLiveFilesMetaData(&live_files);
337   delete db;
338   ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files));
339 }
340 
TEST_F(LdbCmdTest,DumpFileChecksumCRC32)341 TEST_F(LdbCmdTest, DumpFileChecksumCRC32) {
342   Env* base_env = TryLoadCustomOrDefaultEnv();
343   std::unique_ptr<Env> env(NewMemEnv(base_env));
344   Options opts;
345   opts.env = env.get();
346   opts.create_if_missing = true;
347   FileChecksumGenCrc32cFactory* file_checksum_gen_factory =
348       new FileChecksumGenCrc32cFactory();
349   opts.file_checksum_gen_factory.reset(file_checksum_gen_factory);
350 
351   DB* db = nullptr;
352   std::string dbname = test::TmpDir();
353   ASSERT_OK(DB::Open(opts, dbname, &db));
354 
355   WriteOptions wopts;
356   FlushOptions fopts;
357   fopts.wait = true;
358   Random rnd(test::RandomSeed());
359   for (int i = 0; i < 100; i++) {
360     char buf[16];
361     snprintf(buf, sizeof(buf), "%08d", i);
362     std::string v;
363     test::RandomString(&rnd, 100, &v);
364     ASSERT_OK(db->Put(wopts, buf, v));
365   }
366   ASSERT_OK(db->Flush(fopts));
367   for (int i = 50; i < 150; i++) {
368     char buf[16];
369     snprintf(buf, sizeof(buf), "%08d", i);
370     std::string v;
371     test::RandomString(&rnd, 100, &v);
372     ASSERT_OK(db->Put(wopts, buf, v));
373   }
374   ASSERT_OK(db->Flush(fopts));
375   for (int i = 100; i < 200; i++) {
376     char buf[16];
377     snprintf(buf, sizeof(buf), "%08d", i);
378     std::string v;
379     test::RandomString(&rnd, 100, &v);
380     ASSERT_OK(db->Put(wopts, buf, v));
381   }
382   ASSERT_OK(db->Flush(fopts));
383   for (int i = 150; i < 250; i++) {
384     char buf[16];
385     snprintf(buf, sizeof(buf), "%08d", i);
386     std::string v;
387     test::RandomString(&rnd, 100, &v);
388     ASSERT_OK(db->Put(wopts, buf, v));
389   }
390   ASSERT_OK(db->Flush(fopts));
391 
392   char arg1[] = "./ldb";
393   char arg2[1024];
394   snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
395   char arg3[] = "file_checksum_dump";
396   char* argv[] = {arg1, arg2, arg3};
397 
398   ASSERT_EQ(0,
399             LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
400 
401   // Verify each sst file checksum value and checksum name
402   FileChecksumTestHelper fct_helper(opts, db, dbname);
403   ASSERT_OK(fct_helper.VerifyEachFileChecksum());
404 
405   // Manually trigger compaction
406   char b_buf[16];
407   snprintf(b_buf, sizeof(b_buf), "%08d", 0);
408   char e_buf[16];
409   snprintf(e_buf, sizeof(e_buf), "%08d", 249);
410   Slice begin(b_buf);
411   Slice end(e_buf);
412   CompactRangeOptions options;
413   ASSERT_OK(db->CompactRange(options, &begin, &end));
414   // Verify each sst file checksum after compaction
415   FileChecksumTestHelper fct_helper_ac(opts, db, dbname);
416   ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum());
417 
418   ASSERT_EQ(0,
419             LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
420 
421   // Verify the checksum information in memory is the same as that in Manifest;
422   std::vector<LiveFileMetaData> live_files;
423   db->GetLiveFilesMetaData(&live_files);
424   delete db;
425   ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files));
426 }
427 
TEST_F(LdbCmdTest,OptionParsing)428 TEST_F(LdbCmdTest, OptionParsing) {
429   // test parsing flags
430   Options opts;
431   opts.env = TryLoadCustomOrDefaultEnv();
432   {
433     std::vector<std::string> args;
434     args.push_back("scan");
435     args.push_back("--ttl");
436     args.push_back("--timestamp");
437     LDBCommand* command = ROCKSDB_NAMESPACE::LDBCommand::InitFromCmdLineArgs(
438         args, opts, LDBOptions(), nullptr);
439     const std::vector<std::string> flags = command->TEST_GetFlags();
440     EXPECT_EQ(flags.size(), 2);
441     EXPECT_EQ(flags[0], "ttl");
442     EXPECT_EQ(flags[1], "timestamp");
443     delete command;
444   }
445   // test parsing options which contains equal sign in the option value
446   {
447     std::vector<std::string> args;
448     args.push_back("scan");
449     args.push_back("--db=/dev/shm/ldbtest/");
450     args.push_back(
451         "--from='abcd/efg/hijk/lmn/"
452         "opq:__rst.uvw.xyz?a=3+4+bcd+efghi&jk=lm_no&pq=rst-0&uv=wx-8&yz=a&bcd_"
453         "ef=gh.ijk'");
454     LDBCommand* command = ROCKSDB_NAMESPACE::LDBCommand::InitFromCmdLineArgs(
455         args, opts, LDBOptions(), nullptr);
456     const std::map<std::string, std::string> option_map =
457         command->TEST_GetOptionMap();
458     EXPECT_EQ(option_map.at("db"), "/dev/shm/ldbtest/");
459     EXPECT_EQ(option_map.at("from"),
460               "'abcd/efg/hijk/lmn/"
461               "opq:__rst.uvw.xyz?a=3+4+bcd+efghi&jk=lm_no&pq=rst-0&uv=wx-8&yz="
462               "a&bcd_ef=gh.ijk'");
463     delete command;
464   }
465 }
466 
TEST_F(LdbCmdTest,ListFileTombstone)467 TEST_F(LdbCmdTest, ListFileTombstone) {
468   Env* base_env = TryLoadCustomOrDefaultEnv();
469   std::unique_ptr<Env> env(NewMemEnv(base_env));
470   Options opts;
471   opts.env = env.get();
472   opts.create_if_missing = true;
473 
474   DB* db = nullptr;
475   std::string dbname = test::TmpDir();
476   ASSERT_OK(DB::Open(opts, dbname, &db));
477 
478   WriteOptions wopts;
479   ASSERT_OK(db->Put(wopts, "foo", "1"));
480   ASSERT_OK(db->Put(wopts, "bar", "2"));
481 
482   FlushOptions fopts;
483   fopts.wait = true;
484   ASSERT_OK(db->Flush(fopts));
485 
486   ASSERT_OK(db->DeleteRange(wopts, db->DefaultColumnFamily(), "foo", "foo2"));
487   ASSERT_OK(db->DeleteRange(wopts, db->DefaultColumnFamily(), "bar", "foo2"));
488   ASSERT_OK(db->Flush(fopts));
489 
490   delete db;
491 
492   {
493     char arg1[] = "./ldb";
494     char arg2[1024];
495     snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
496     char arg3[] = "list_file_range_deletes";
497     char* argv[] = {arg1, arg2, arg3};
498 
499     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
500         "ListFileRangeDeletesCommand::DoCommand:BeforePrint", [&](void* arg) {
501           std::string* out_str = reinterpret_cast<std::string*>(arg);
502 
503           // Count number of tombstones printed
504           int num_tb = 0;
505           const std::string kFingerprintStr = "start: ";
506           auto offset = out_str->find(kFingerprintStr);
507           while (offset != std::string::npos) {
508             num_tb++;
509             offset =
510                 out_str->find(kFingerprintStr, offset + kFingerprintStr.size());
511           }
512           EXPECT_EQ(2, num_tb);
513         });
514     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
515 
516     ASSERT_EQ(
517         0, LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
518 
519     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
520     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
521   }
522 
523   // Test the case of limiting tombstones
524   {
525     char arg1[] = "./ldb";
526     char arg2[1024];
527     snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
528     char arg3[] = "list_file_range_deletes";
529     char arg4[] = "--max_keys=1";
530     char* argv[] = {arg1, arg2, arg3, arg4};
531 
532     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
533         "ListFileRangeDeletesCommand::DoCommand:BeforePrint", [&](void* arg) {
534           std::string* out_str = reinterpret_cast<std::string*>(arg);
535 
536           // Count number of tombstones printed
537           int num_tb = 0;
538           const std::string kFingerprintStr = "start: ";
539           auto offset = out_str->find(kFingerprintStr);
540           while (offset != std::string::npos) {
541             num_tb++;
542             offset =
543                 out_str->find(kFingerprintStr, offset + kFingerprintStr.size());
544           }
545           EXPECT_EQ(1, num_tb);
546         });
547     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
548 
549     ASSERT_EQ(
550         0, LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr));
551 
552     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
553     ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
554   }
555 }
556 }  // namespace ROCKSDB_NAMESPACE
557 
558 #ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
559 extern "C" {
560 void RegisterCustomObjects(int argc, char** argv);
561 }
562 #else
RegisterCustomObjects(int,char **)563 void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
564 #endif  // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
565 
main(int argc,char ** argv)566 int main(int argc, char** argv) {
567   ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
568   ::testing::InitGoogleTest(&argc, argv);
569   RegisterCustomObjects(argc, argv);
570   return RUN_ALL_TESTS();
571 }
572 #else
573 #include <stdio.h>
574 
main(int,char **)575 int main(int /*argc*/, char** /*argv*/) {
576   fprintf(stderr, "SKIPPED as LDBCommand is not supported in ROCKSDB_LITE\n");
577   return 0;
578 }
579 
580 #endif  // ROCKSDB_LITE
581