]> git.proxmox.com Git - ceph.git/blame - ceph/src/rocksdb/db/log_test.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / rocksdb / db / log_test.cc
CommitLineData
7c673cae 1// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
11fdf7f2
TL
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).
7c673cae
FG
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 "db/log_reader.h"
11#include "db/log_writer.h"
12#include "rocksdb/env.h"
13#include "util/coding.h"
14#include "util/crc32c.h"
15#include "util/file_reader_writer.h"
16#include "util/random.h"
17#include "util/testharness.h"
18#include "util/testutil.h"
19
20namespace rocksdb {
21namespace log {
22
23// Construct a string of the specified length made out of the supplied
24// partial string.
25static std::string BigString(const std::string& partial_string, size_t n) {
26 std::string result;
27 while (result.size() < n) {
28 result.append(partial_string);
29 }
30 result.resize(n);
31 return result;
32}
33
34// Construct a string from a number
35static std::string NumberString(int n) {
36 char buf[50];
37 snprintf(buf, sizeof(buf), "%d.", n);
38 return std::string(buf);
39}
40
41// Return a skewed potentially long string
42static std::string RandomSkewedString(int i, Random* rnd) {
43 return BigString(NumberString(i), rnd->Skewed(17));
44}
45
46class LogTest : public ::testing::TestWithParam<int> {
47 private:
48 class StringSource : public SequentialFile {
49 public:
50 Slice& contents_;
51 bool force_error_;
52 size_t force_error_position_;
53 bool force_eof_;
54 size_t force_eof_position_;
55 bool returned_partial_;
56 explicit StringSource(Slice& contents) :
57 contents_(contents),
58 force_error_(false),
59 force_error_position_(0),
60 force_eof_(false),
61 force_eof_position_(0),
62 returned_partial_(false) { }
63
64 virtual Status Read(size_t n, Slice* result, char* scratch) override {
65 EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error";
66
67 if (force_error_) {
68 if (force_error_position_ >= n) {
69 force_error_position_ -= n;
70 } else {
71 *result = Slice(contents_.data(), force_error_position_);
72 contents_.remove_prefix(force_error_position_);
73 force_error_ = false;
74 returned_partial_ = true;
75 return Status::Corruption("read error");
76 }
77 }
78
79 if (contents_.size() < n) {
80 n = contents_.size();
81 returned_partial_ = true;
82 }
83
84 if (force_eof_) {
85 if (force_eof_position_ >= n) {
86 force_eof_position_ -= n;
87 } else {
88 force_eof_ = false;
89 n = force_eof_position_;
90 returned_partial_ = true;
91 }
92 }
93
94 // By using scratch we ensure that caller has control over the
95 // lifetime of result.data()
96 memcpy(scratch, contents_.data(), n);
97 *result = Slice(scratch, n);
98
99 contents_.remove_prefix(n);
100 return Status::OK();
101 }
102
103 virtual Status Skip(uint64_t n) override {
104 if (n > contents_.size()) {
105 contents_.clear();
106 return Status::NotFound("in-memory file skipepd past end");
107 }
108
109 contents_.remove_prefix(n);
110
111 return Status::OK();
112 }
113 };
114
115 class ReportCollector : public Reader::Reporter {
116 public:
117 size_t dropped_bytes_;
118 std::string message_;
119
120 ReportCollector() : dropped_bytes_(0) { }
121 virtual void Corruption(size_t bytes, const Status& status) override {
122 dropped_bytes_ += bytes;
123 message_.append(status.ToString());
124 }
125 };
126
127 std::string& dest_contents() {
128 auto dest =
129 dynamic_cast<test::StringSink*>(writer_.file()->writable_file());
130 assert(dest);
131 return dest->contents_;
132 }
133
134 const std::string& dest_contents() const {
135 auto dest =
136 dynamic_cast<const test::StringSink*>(writer_.file()->writable_file());
137 assert(dest);
138 return dest->contents_;
139 }
140
141 void reset_source_contents() {
142 auto src = dynamic_cast<StringSource*>(reader_.file()->file());
143 assert(src);
144 src->contents_ = dest_contents();
145 }
146
147 Slice reader_contents_;
148 unique_ptr<WritableFileWriter> dest_holder_;
149 unique_ptr<SequentialFileReader> source_holder_;
150 ReportCollector report_;
151 Writer writer_;
152 Reader reader_;
153
154 // Record metadata for testing initial offset functionality
155 static size_t initial_offset_record_sizes_[];
156 uint64_t initial_offset_last_record_offsets_[4];
157
158 public:
159 LogTest()
160 : reader_contents_(),
161 dest_holder_(test::GetWritableFileWriter(
11fdf7f2
TL
162 new test::StringSink(&reader_contents_), "" /* don't care */)),
163 source_holder_(test::GetSequentialFileReader(
164 new StringSource(reader_contents_), "" /* file name */)),
7c673cae 165 writer_(std::move(dest_holder_), 123, GetParam()),
11fdf7f2
TL
166 reader_(nullptr, std::move(source_holder_), &report_,
167 true /* checksum */, 123 /* log_number */) {
7c673cae
FG
168 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
169 initial_offset_last_record_offsets_[0] = 0;
170 initial_offset_last_record_offsets_[1] = header_size + 10000;
171 initial_offset_last_record_offsets_[2] = 2 * (header_size + 10000);
172 initial_offset_last_record_offsets_[3] = 2 * (header_size + 10000) +
173 (2 * log::kBlockSize - 1000) +
174 3 * header_size;
175 }
176
177 Slice* get_reader_contents() { return &reader_contents_; }
178
179 void Write(const std::string& msg) {
180 writer_.AddRecord(Slice(msg));
181 }
182
183 size_t WrittenBytes() const {
184 return dest_contents().size();
185 }
186
187 std::string Read(const WALRecoveryMode wal_recovery_mode =
188 WALRecoveryMode::kTolerateCorruptedTailRecords) {
189 std::string scratch;
190 Slice record;
191 if (reader_.ReadRecord(&record, &scratch, wal_recovery_mode)) {
192 return record.ToString();
193 } else {
194 return "EOF";
195 }
196 }
197
11fdf7f2 198 void IncrementByte(int offset, char delta) {
7c673cae
FG
199 dest_contents()[offset] += delta;
200 }
201
202 void SetByte(int offset, char new_byte) {
203 dest_contents()[offset] = new_byte;
204 }
205
206 void ShrinkSize(int bytes) {
207 auto dest =
208 dynamic_cast<test::StringSink*>(writer_.file()->writable_file());
209 assert(dest);
210 dest->Drop(bytes);
211 }
212
213 void FixChecksum(int header_offset, int len, bool recyclable) {
214 // Compute crc of type/len/data
215 int header_size = recyclable ? kRecyclableHeaderSize : kHeaderSize;
216 uint32_t crc = crc32c::Value(&dest_contents()[header_offset + 6],
217 header_size - 6 + len);
218 crc = crc32c::Mask(crc);
219 EncodeFixed32(&dest_contents()[header_offset], crc);
220 }
221
222 void ForceError(size_t position = 0) {
223 auto src = dynamic_cast<StringSource*>(reader_.file()->file());
224 src->force_error_ = true;
225 src->force_error_position_ = position;
226 }
227
228 size_t DroppedBytes() const {
229 return report_.dropped_bytes_;
230 }
231
232 std::string ReportMessage() const {
233 return report_.message_;
234 }
235
236 void ForceEOF(size_t position = 0) {
237 auto src = dynamic_cast<StringSource*>(reader_.file()->file());
238 src->force_eof_ = true;
239 src->force_eof_position_ = position;
240 }
241
242 void UnmarkEOF() {
243 auto src = dynamic_cast<StringSource*>(reader_.file()->file());
244 src->returned_partial_ = false;
245 reader_.UnmarkEOF();
246 }
247
248 bool IsEOF() {
249 return reader_.IsEOF();
250 }
251
252 // Returns OK iff recorded error message contains "msg"
253 std::string MatchError(const std::string& msg) const {
254 if (report_.message_.find(msg) == std::string::npos) {
255 return report_.message_;
256 } else {
257 return "OK";
258 }
259 }
260
261 void WriteInitialOffsetLog() {
262 for (int i = 0; i < 4; i++) {
263 std::string record(initial_offset_record_sizes_[i],
264 static_cast<char>('a' + i));
265 Write(record);
266 }
267 }
268
7c673cae
FG
269};
270
271size_t LogTest::initial_offset_record_sizes_[] =
272 {10000, // Two sizable records in first block
273 10000,
274 2 * log::kBlockSize - 1000, // Span three blocks
275 1};
276
277TEST_P(LogTest, Empty) { ASSERT_EQ("EOF", Read()); }
278
279TEST_P(LogTest, ReadWrite) {
280 Write("foo");
281 Write("bar");
282 Write("");
283 Write("xxxx");
284 ASSERT_EQ("foo", Read());
285 ASSERT_EQ("bar", Read());
286 ASSERT_EQ("", Read());
287 ASSERT_EQ("xxxx", Read());
288 ASSERT_EQ("EOF", Read());
289 ASSERT_EQ("EOF", Read()); // Make sure reads at eof work
290}
291
292TEST_P(LogTest, ManyBlocks) {
293 for (int i = 0; i < 100000; i++) {
294 Write(NumberString(i));
295 }
296 for (int i = 0; i < 100000; i++) {
297 ASSERT_EQ(NumberString(i), Read());
298 }
299 ASSERT_EQ("EOF", Read());
300}
301
302TEST_P(LogTest, Fragmentation) {
303 Write("small");
304 Write(BigString("medium", 50000));
305 Write(BigString("large", 100000));
306 ASSERT_EQ("small", Read());
307 ASSERT_EQ(BigString("medium", 50000), Read());
308 ASSERT_EQ(BigString("large", 100000), Read());
309 ASSERT_EQ("EOF", Read());
310}
311
312TEST_P(LogTest, MarginalTrailer) {
313 // Make a trailer that is exactly the same length as an empty record.
314 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
315 const int n = kBlockSize - 2 * header_size;
316 Write(BigString("foo", n));
317 ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes());
318 Write("");
319 Write("bar");
320 ASSERT_EQ(BigString("foo", n), Read());
321 ASSERT_EQ("", Read());
322 ASSERT_EQ("bar", Read());
323 ASSERT_EQ("EOF", Read());
324}
325
326TEST_P(LogTest, MarginalTrailer2) {
327 // Make a trailer that is exactly the same length as an empty record.
328 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
329 const int n = kBlockSize - 2 * header_size;
330 Write(BigString("foo", n));
331 ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes());
332 Write("bar");
333 ASSERT_EQ(BigString("foo", n), Read());
334 ASSERT_EQ("bar", Read());
335 ASSERT_EQ("EOF", Read());
336 ASSERT_EQ(0U, DroppedBytes());
337 ASSERT_EQ("", ReportMessage());
338}
339
340TEST_P(LogTest, ShortTrailer) {
341 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
342 const int n = kBlockSize - 2 * header_size + 4;
343 Write(BigString("foo", n));
344 ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes());
345 Write("");
346 Write("bar");
347 ASSERT_EQ(BigString("foo", n), Read());
348 ASSERT_EQ("", Read());
349 ASSERT_EQ("bar", Read());
350 ASSERT_EQ("EOF", Read());
351}
352
353TEST_P(LogTest, AlignedEof) {
354 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
355 const int n = kBlockSize - 2 * header_size + 4;
356 Write(BigString("foo", n));
357 ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes());
358 ASSERT_EQ(BigString("foo", n), Read());
359 ASSERT_EQ("EOF", Read());
360}
361
362TEST_P(LogTest, RandomRead) {
363 const int N = 500;
364 Random write_rnd(301);
365 for (int i = 0; i < N; i++) {
366 Write(RandomSkewedString(i, &write_rnd));
367 }
368 Random read_rnd(301);
369 for (int i = 0; i < N; i++) {
370 ASSERT_EQ(RandomSkewedString(i, &read_rnd), Read());
371 }
372 ASSERT_EQ("EOF", Read());
373}
374
375// Tests of all the error paths in log_reader.cc follow:
376
377TEST_P(LogTest, ReadError) {
378 Write("foo");
379 ForceError();
380 ASSERT_EQ("EOF", Read());
381 ASSERT_EQ((unsigned int)kBlockSize, DroppedBytes());
382 ASSERT_EQ("OK", MatchError("read error"));
383}
384
385TEST_P(LogTest, BadRecordType) {
386 Write("foo");
387 // Type is stored in header[6]
388 IncrementByte(6, 100);
389 FixChecksum(0, 3, false);
390 ASSERT_EQ("EOF", Read());
391 ASSERT_EQ(3U, DroppedBytes());
392 ASSERT_EQ("OK", MatchError("unknown record type"));
393}
394
395TEST_P(LogTest, TruncatedTrailingRecordIsIgnored) {
396 Write("foo");
397 ShrinkSize(4); // Drop all payload as well as a header byte
398 ASSERT_EQ("EOF", Read());
399 // Truncated last record is ignored, not treated as an error
400 ASSERT_EQ(0U, DroppedBytes());
401 ASSERT_EQ("", ReportMessage());
402}
403
404TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) {
405 Write("foo");
406 ShrinkSize(4); // Drop all payload as well as a header byte
407 ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
408 // Truncated last record is ignored, not treated as an error
409 ASSERT_GT(DroppedBytes(), 0U);
410 ASSERT_EQ("OK", MatchError("Corruption: truncated header"));
411}
412
413TEST_P(LogTest, BadLength) {
414 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
415 const int kPayloadSize = kBlockSize - header_size;
416 Write(BigString("bar", kPayloadSize));
417 Write("foo");
418 // Least significant size byte is stored in header[4].
419 IncrementByte(4, 1);
420 if (!GetParam()) {
421 ASSERT_EQ("foo", Read());
422 ASSERT_EQ(kBlockSize, DroppedBytes());
423 ASSERT_EQ("OK", MatchError("bad record length"));
424 } else {
425 ASSERT_EQ("EOF", Read());
426 }
427}
428
429TEST_P(LogTest, BadLengthAtEndIsIgnored) {
430 Write("foo");
431 ShrinkSize(1);
432 ASSERT_EQ("EOF", Read());
433 ASSERT_EQ(0U, DroppedBytes());
434 ASSERT_EQ("", ReportMessage());
435}
436
437TEST_P(LogTest, BadLengthAtEndIsNotIgnored) {
438 Write("foo");
439 ShrinkSize(1);
440 ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
441 ASSERT_GT(DroppedBytes(), 0U);
442 ASSERT_EQ("OK", MatchError("Corruption: truncated header"));
443}
444
445TEST_P(LogTest, ChecksumMismatch) {
446 Write("foooooo");
447 IncrementByte(0, 14);
448 ASSERT_EQ("EOF", Read());
449 if (!GetParam()) {
450 ASSERT_EQ(14U, DroppedBytes());
451 ASSERT_EQ("OK", MatchError("checksum mismatch"));
452 } else {
453 ASSERT_EQ(0U, DroppedBytes());
454 ASSERT_EQ("", ReportMessage());
455 }
456}
457
458TEST_P(LogTest, UnexpectedMiddleType) {
459 Write("foo");
11fdf7f2 460 SetByte(6, static_cast<char>(GetParam() ? kRecyclableMiddleType : kMiddleType));
7c673cae
FG
461 FixChecksum(0, 3, !!GetParam());
462 ASSERT_EQ("EOF", Read());
463 ASSERT_EQ(3U, DroppedBytes());
464 ASSERT_EQ("OK", MatchError("missing start"));
465}
466
467TEST_P(LogTest, UnexpectedLastType) {
468 Write("foo");
11fdf7f2 469 SetByte(6, static_cast<char>(GetParam() ? kRecyclableLastType : kLastType));
7c673cae
FG
470 FixChecksum(0, 3, !!GetParam());
471 ASSERT_EQ("EOF", Read());
472 ASSERT_EQ(3U, DroppedBytes());
473 ASSERT_EQ("OK", MatchError("missing start"));
474}
475
476TEST_P(LogTest, UnexpectedFullType) {
477 Write("foo");
478 Write("bar");
11fdf7f2 479 SetByte(6, static_cast<char>(GetParam() ? kRecyclableFirstType : kFirstType));
7c673cae
FG
480 FixChecksum(0, 3, !!GetParam());
481 ASSERT_EQ("bar", Read());
482 ASSERT_EQ("EOF", Read());
483 ASSERT_EQ(3U, DroppedBytes());
484 ASSERT_EQ("OK", MatchError("partial record without end"));
485}
486
487TEST_P(LogTest, UnexpectedFirstType) {
488 Write("foo");
489 Write(BigString("bar", 100000));
11fdf7f2 490 SetByte(6, static_cast<char>(GetParam() ? kRecyclableFirstType : kFirstType));
7c673cae
FG
491 FixChecksum(0, 3, !!GetParam());
492 ASSERT_EQ(BigString("bar", 100000), Read());
493 ASSERT_EQ("EOF", Read());
494 ASSERT_EQ(3U, DroppedBytes());
495 ASSERT_EQ("OK", MatchError("partial record without end"));
496}
497
498TEST_P(LogTest, MissingLastIsIgnored) {
499 Write(BigString("bar", kBlockSize));
500 // Remove the LAST block, including header.
501 ShrinkSize(14);
502 ASSERT_EQ("EOF", Read());
503 ASSERT_EQ("", ReportMessage());
504 ASSERT_EQ(0U, DroppedBytes());
505}
506
507TEST_P(LogTest, MissingLastIsNotIgnored) {
508 Write(BigString("bar", kBlockSize));
509 // Remove the LAST block, including header.
510 ShrinkSize(14);
511 ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
512 ASSERT_GT(DroppedBytes(), 0U);
513 ASSERT_EQ("OK", MatchError("Corruption: error reading trailing data"));
514}
515
516TEST_P(LogTest, PartialLastIsIgnored) {
517 Write(BigString("bar", kBlockSize));
518 // Cause a bad record length in the LAST block.
519 ShrinkSize(1);
520 ASSERT_EQ("EOF", Read());
521 ASSERT_EQ("", ReportMessage());
522 ASSERT_EQ(0U, DroppedBytes());
523}
524
525TEST_P(LogTest, PartialLastIsNotIgnored) {
526 Write(BigString("bar", kBlockSize));
527 // Cause a bad record length in the LAST block.
528 ShrinkSize(1);
529 ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
530 ASSERT_GT(DroppedBytes(), 0U);
531 ASSERT_EQ("OK", MatchError(
532 "Corruption: truncated headerCorruption: "
533 "error reading trailing data"));
534}
535
536TEST_P(LogTest, ErrorJoinsRecords) {
537 // Consider two fragmented records:
538 // first(R1) last(R1) first(R2) last(R2)
539 // where the middle two fragments disappear. We do not want
540 // first(R1),last(R2) to get joined and returned as a valid record.
541
542 // Write records that span two blocks
543 Write(BigString("foo", kBlockSize));
544 Write(BigString("bar", kBlockSize));
545 Write("correct");
546
547 // Wipe the middle block
548 for (unsigned int offset = kBlockSize; offset < 2*kBlockSize; offset++) {
549 SetByte(offset, 'x');
550 }
551
552 if (!GetParam()) {
553 ASSERT_EQ("correct", Read());
554 ASSERT_EQ("EOF", Read());
555 size_t dropped = DroppedBytes();
556 ASSERT_LE(dropped, 2 * kBlockSize + 100);
557 ASSERT_GE(dropped, 2 * kBlockSize);
558 } else {
559 ASSERT_EQ("EOF", Read());
560 }
561}
562
7c673cae
FG
563TEST_P(LogTest, ClearEofSingleBlock) {
564 Write("foo");
565 Write("bar");
566 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
567 ForceEOF(3 + header_size + 2);
568 ASSERT_EQ("foo", Read());
569 UnmarkEOF();
570 ASSERT_EQ("bar", Read());
571 ASSERT_TRUE(IsEOF());
572 ASSERT_EQ("EOF", Read());
573 Write("xxx");
574 UnmarkEOF();
575 ASSERT_EQ("xxx", Read());
576 ASSERT_TRUE(IsEOF());
577}
578
579TEST_P(LogTest, ClearEofMultiBlock) {
580 size_t num_full_blocks = 5;
581 int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
582 size_t n = (kBlockSize - header_size) * num_full_blocks + 25;
583 Write(BigString("foo", n));
584 Write(BigString("bar", n));
585 ForceEOF(n + num_full_blocks * header_size + header_size + 3);
586 ASSERT_EQ(BigString("foo", n), Read());
587 ASSERT_TRUE(IsEOF());
588 UnmarkEOF();
589 ASSERT_EQ(BigString("bar", n), Read());
590 ASSERT_TRUE(IsEOF());
591 Write(BigString("xxx", n));
592 UnmarkEOF();
593 ASSERT_EQ(BigString("xxx", n), Read());
594 ASSERT_TRUE(IsEOF());
595}
596
597TEST_P(LogTest, ClearEofError) {
598 // If an error occurs during Read() in UnmarkEOF(), the records contained
599 // in the buffer should be returned on subsequent calls of ReadRecord()
600 // until no more full records are left, whereafter ReadRecord() should return
601 // false to indicate that it cannot read any further.
602
603 Write("foo");
604 Write("bar");
605 UnmarkEOF();
606 ASSERT_EQ("foo", Read());
607 ASSERT_TRUE(IsEOF());
608 Write("xxx");
609 ForceError(0);
610 UnmarkEOF();
611 ASSERT_EQ("bar", Read());
612 ASSERT_EQ("EOF", Read());
613}
614
615TEST_P(LogTest, ClearEofError2) {
616 Write("foo");
617 Write("bar");
618 UnmarkEOF();
619 ASSERT_EQ("foo", Read());
620 Write("xxx");
621 ForceError(3);
622 UnmarkEOF();
623 ASSERT_EQ("bar", Read());
624 ASSERT_EQ("EOF", Read());
625 ASSERT_EQ(3U, DroppedBytes());
626 ASSERT_EQ("OK", MatchError("read error"));
627}
628
629TEST_P(LogTest, Recycle) {
630 if (!GetParam()) {
631 return; // test is only valid for recycled logs
632 }
633 Write("foo");
634 Write("bar");
635 Write("baz");
636 Write("bif");
637 Write("blitz");
638 while (get_reader_contents()->size() < log::kBlockSize * 2) {
639 Write("xxxxxxxxxxxxxxxx");
640 }
641 unique_ptr<WritableFileWriter> dest_holder(test::GetWritableFileWriter(
11fdf7f2
TL
642 new test::OverwritingStringSink(get_reader_contents()),
643 "" /* don't care */));
7c673cae
FG
644 Writer recycle_writer(std::move(dest_holder), 123, true);
645 recycle_writer.AddRecord(Slice("foooo"));
646 recycle_writer.AddRecord(Slice("bar"));
647 ASSERT_GE(get_reader_contents()->size(), log::kBlockSize * 2);
648 ASSERT_EQ("foooo", Read());
649 ASSERT_EQ("bar", Read());
650 ASSERT_EQ("EOF", Read());
651}
652
653INSTANTIATE_TEST_CASE_P(bool, LogTest, ::testing::Values(0, 2));
654
655} // namespace log
656} // namespace rocksdb
657
658int main(int argc, char** argv) {
659 ::testing::InitGoogleTest(&argc, argv);
660 return RUN_ALL_TESTS();
661}