]>
Commit | Line | Data |
---|---|---|
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 | ||
20 | namespace rocksdb { | |
21 | namespace log { | |
22 | ||
23 | // Construct a string of the specified length made out of the supplied | |
24 | // partial string. | |
25 | static 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 | |
35 | static 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 | |
42 | static std::string RandomSkewedString(int i, Random* rnd) { | |
43 | return BigString(NumberString(i), rnd->Skewed(17)); | |
44 | } | |
45 | ||
46 | class 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 | ||
271 | size_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 | ||
277 | TEST_P(LogTest, Empty) { ASSERT_EQ("EOF", Read()); } | |
278 | ||
279 | TEST_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 | ||
292 | TEST_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 | ||
302 | TEST_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 | ||
312 | TEST_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 | ||
326 | TEST_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 | ||
340 | TEST_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 | ||
353 | TEST_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 | ||
362 | TEST_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 | ||
377 | TEST_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 | ||
385 | TEST_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 | ||
395 | TEST_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 | ||
404 | TEST_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 | ||
413 | TEST_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 | ||
429 | TEST_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 | ||
437 | TEST_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 | ||
445 | TEST_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 | ||
458 | TEST_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 | ||
467 | TEST_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 | ||
476 | TEST_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 | ||
487 | TEST_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 | ||
498 | TEST_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 | ||
507 | TEST_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 | ||
516 | TEST_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 | ||
525 | TEST_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 | ||
536 | TEST_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 |
563 | TEST_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 | ||
579 | TEST_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 | ||
597 | TEST_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 | ||
615 | TEST_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 | ||
629 | TEST_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 | ||
653 | INSTANTIATE_TEST_CASE_P(bool, LogTest, ::testing::Values(0, 2)); | |
654 | ||
655 | } // namespace log | |
656 | } // namespace rocksdb | |
657 | ||
658 | int main(int argc, char** argv) { | |
659 | ::testing::InitGoogleTest(&argc, argv); | |
660 | return RUN_ALL_TESTS(); | |
661 | } |