YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/util/pb_util-test.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
33
#include <memory>
34
#include <sstream>
35
#include <string>
36
#include <vector>
37
38
#include <google/protobuf/descriptor.pb.h>
39
#include <gtest/gtest.h>
40
41
#include "yb/util/env_util.h"
42
#include "yb/util/memenv/memenv.h"
43
#include "yb/util/pb_util-internal.h"
44
#include "yb/util/pb_util.h"
45
#include "yb/util/proto_container_test.pb.h"
46
#include "yb/util/proto_container_test2.pb.h"
47
#include "yb/util/proto_container_test3.pb.h"
48
#include "yb/util/protobuf_util.h"
49
#include "yb/util/result.h"
50
#include "yb/util/status.h"
51
#include "yb/util/test_macros.h"
52
#include "yb/util/test_util.h"
53
54
namespace yb {
55
namespace pb_util {
56
57
using google::protobuf::FileDescriptorSet;
58
using internal::WritableFileOutputStream;
59
using std::ostringstream;
60
using std::shared_ptr;
61
using std::string;
62
using std::vector;
63
64
static const char* kTestFileName = "pb_container.meta";
65
static const char* kTestKeyvalName = "my-key";
66
static const int kTestKeyvalValue = 1;
67
static const std::string kTestString = "test-string";
68
69
class TestPBUtil : public YBTest {
70
 public:
71
10
  void SetUp() override {
72
10
    YBTest::SetUp();
73
10
    path_ = GetTestPath(kTestFileName);
74
10
  }
75
76
 protected:
77
  // Create a container file with expected values.
78
  // Since this is a unit test class, and we want it to be fast, we do not
79
  // fsync by default.
80
  Status CreateKnownGoodContainerFile(CreateMode create = OVERWRITE,
81
                                      SyncMode sync = NO_SYNC);
82
83
  // XORs the data in the specified range of the file at the given path.
84
  Status BitFlipFileByteRange(const string& path, uint64_t offset, uint64_t length);
85
86
  void DumpPBCToString(const string& path, bool oneline_output, string* ret);
87
88
  // Output file name for most unit tests.
89
  string path_;
90
};
91
92
12
Status TestPBUtil::CreateKnownGoodContainerFile(CreateMode create, SyncMode sync) {
93
12
  ProtoContainerTestPB test_pb;
94
12
  test_pb.set_name(kTestKeyvalName);
95
12
  test_pb.set_value(kTestKeyvalValue);
96
12
  return WritePBContainerToPath(env_.get(), path_, test_pb, create, sync);
97
12
}
98
99
5
Status TestPBUtil::BitFlipFileByteRange(const string& path, uint64_t offset, uint64_t length) {
100
5
  faststring buf;
101
  // Read the data from disk.
102
5
  {
103
5
    std::unique_ptr<RandomAccessFile> file;
104
5
    RETURN_NOT_OK(env_->NewRandomAccessFile(path, &file));
105
5
    uint64_t size = VERIFY_RESULT(file->Size());
106
5
    Slice slice;
107
5
    faststring scratch;
108
5
    scratch.resize(size);
109
5
    RETURN_NOT_OK(env_util::ReadFully(file.get(), 0, size, &slice, scratch.data()));
110
5
    buf.append(slice.data(), slice.size());
111
5
  }
112
113
  // Flip the bits.
114
15
  for (uint64_t i = 0; i < length; i++) {
115
10
    uint8_t* addr = buf.data() + offset + i;
116
10
    *addr = ~*addr;
117
10
  }
118
119
  // Write the data back to disk.
120
5
  std::unique_ptr<WritableFile> file;
121
5
  RETURN_NOT_OK(env_->NewWritableFile(path, &file));
122
5
  RETURN_NOT_OK(file->Append(buf));
123
5
  RETURN_NOT_OK(file->Close());
124
125
5
  return Status::OK();
126
5
}
127
128
1
TEST_F(TestPBUtil, TestWritableFileOutputStream) {
129
1
  std::unique_ptr<Env> env(NewMemEnv(Env::Default()));
130
1
  shared_ptr<WritableFile> file;
131
1
  ASSERT_OK(env_util::OpenFileForWrite(env.get(), "/test", &file));
132
133
1
  WritableFileOutputStream stream(file.get(), 4096);
134
135
1
  void* buf;
136
1
  int size;
137
138
  // First call should yield the whole buffer.
139
1
  ASSERT_TRUE(stream.Next(&buf, &size));
140
1
  ASSERT_EQ(4096, size);
141
1
  ASSERT_EQ(4096, stream.ByteCount());
142
143
  // Backup 1000 and the next call should yield 1000
144
1
  stream.BackUp(1000);
145
1
  ASSERT_EQ(3096, stream.ByteCount());
146
147
1
  ASSERT_TRUE(stream.Next(&buf, &size));
148
1
  ASSERT_EQ(1000, size);
149
150
  // Another call should flush and yield a new buffer of 4096
151
1
  ASSERT_TRUE(stream.Next(&buf, &size));
152
1
  ASSERT_EQ(4096, size);
153
1
  ASSERT_EQ(8192, stream.ByteCount());
154
155
  // Should be able to backup to 7192
156
1
  stream.BackUp(1000);
157
1
  ASSERT_EQ(7192, stream.ByteCount());
158
159
  // Flushing shouldn't change written count.
160
1
  ASSERT_TRUE(stream.Flush());
161
1
  ASSERT_EQ(7192, stream.ByteCount());
162
163
  // Since we just flushed, we should get another full buffer.
164
1
  ASSERT_TRUE(stream.Next(&buf, &size));
165
1
  ASSERT_EQ(4096, size);
166
1
  ASSERT_EQ(7192 + 4096, stream.ByteCount());
167
168
1
  ASSERT_TRUE(stream.Flush());
169
170
1
  ASSERT_EQ(stream.ByteCount(), file->Size());
171
1
}
172
173
// Basic read/write test.
174
1
TEST_F(TestPBUtil, TestPBContainerSimple) {
175
  // Exercise both the SYNC and NO_SYNC codepaths, despite the fact that we
176
  // aren't able to observe a difference in the test.
177
1
  vector<SyncMode> modes = { SYNC, NO_SYNC };
178
2
  for (SyncMode mode : modes) {
179
180
    // Write the file.
181
2
    ASSERT_OK(CreateKnownGoodContainerFile(NO_OVERWRITE, mode));
182
183
    // Read it back, should validate and contain the expected values.
184
2
    ProtoContainerTestPB test_pb;
185
2
    ASSERT_OK(ReadPBContainerFromPath(env_.get(), path_, &test_pb));
186
2
    ASSERT_EQ(kTestKeyvalName, test_pb.name());
187
2
    ASSERT_EQ(kTestKeyvalValue, test_pb.value());
188
189
    // Delete the file.
190
2
    ASSERT_OK(env_->DeleteFile(path_));
191
2
  }
192
1
}
193
194
// Corruption / various failure mode test.
195
1
TEST_F(TestPBUtil, TestPBContainerCorruption) {
196
  // Test that we indicate when the file does not exist.
197
1
  ProtoContainerTestPB test_pb;
198
1
  Status s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
199
2
  ASSERT_TRUE(s.IsNotFound()) << "Should not be found: " << path_ << ": " << s.ToString();
200
201
  // Test that an empty file looks like corruption.
202
1
  {
203
    // Create the empty file.
204
1
    std::unique_ptr<WritableFile> file;
205
1
    ASSERT_OK(env_->NewWritableFile(path_, &file));
206
1
    ASSERT_OK(file->Close());
207
1
  }
208
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
209
2
  ASSERT_TRUE(s.IsCorruption()) << "Should be zero length: " << path_ << ": " << s.ToString();
210
1
  ASSERT_STR_CONTAINS(s.ToString(), "File size not large enough to be valid");
211
212
  // Test truncated file.
213
1
  ASSERT_OK(CreateKnownGoodContainerFile());
214
1
  uint64_t known_good_size = ASSERT_RESULT(env_->GetFileSize(path_));
215
1
  int ret = truncate(path_.c_str(), known_good_size - 2);
216
1
  if (ret != 0) {
217
0
    PLOG(ERROR) << "truncate() of file " << path_ << " failed";
218
0
    FAIL();
219
0
  }
220
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
221
2
  ASSERT_TRUE(s.IsCorruption()) << "Should be incorrect size: " << path_ << ": " << s.ToString();
222
1
  ASSERT_STR_CONTAINS(s.ToString(), "File size not large enough to be valid");
223
224
  // Test corrupted magic.
225
1
  ASSERT_OK(CreateKnownGoodContainerFile());
226
1
  ASSERT_OK(BitFlipFileByteRange(path_, 0, 2));
227
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
228
2
  ASSERT_TRUE(s.IsCorruption()) << "Should have invalid magic: " << path_ << ": " << s.ToString();
229
1
  ASSERT_STR_CONTAINS(s.ToString(), "Invalid magic number");
230
231
  // Test corrupted version.
232
1
  ASSERT_OK(CreateKnownGoodContainerFile());
233
1
  ASSERT_OK(BitFlipFileByteRange(path_, 8, 2));
234
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
235
2
  ASSERT_TRUE(s.IsNotSupported()) << "Should have unsupported version number: " << path_ << ": "
236
2
                                  << s.ToString();
237
1
  ASSERT_STR_CONTAINS(s.ToString(), "we only support version 1");
238
239
  // Test corrupted size.
240
1
  ASSERT_OK(CreateKnownGoodContainerFile());
241
1
  ASSERT_OK(BitFlipFileByteRange(path_, 12, 2));
242
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
243
2
  ASSERT_TRUE(s.IsCorruption()) << "Should be incorrect size: " << path_ << ": " << s.ToString();
244
1
  ASSERT_STR_CONTAINS(s.ToString(), "File size not large enough to be valid");
245
246
  // Test corrupted data (looks like bad checksum).
247
1
  ASSERT_OK(CreateKnownGoodContainerFile());
248
1
  ASSERT_OK(BitFlipFileByteRange(path_, 16, 2));
249
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
250
2
  ASSERT_TRUE(s.IsCorruption()) << "Should be incorrect checksum: " << path_ << ": "
251
2
                                << s.ToString();
252
1
  ASSERT_STR_CONTAINS(s.ToString(), "Incorrect checksum");
253
254
  // Test corrupted checksum.
255
1
  ASSERT_OK(CreateKnownGoodContainerFile());
256
1
  ASSERT_OK(BitFlipFileByteRange(path_, known_good_size - 4, 2));
257
1
  s = ReadPBContainerFromPath(env_.get(), path_, &test_pb);
258
2
  ASSERT_TRUE(s.IsCorruption()) << "Should be incorrect checksum: " << path_ << ": "
259
2
                                << s.ToString();
260
1
  ASSERT_STR_CONTAINS(s.ToString(), "Incorrect checksum");
261
1
}
262
263
1
TEST_F(TestPBUtil, TestMultipleMessages) {
264
1
  ProtoContainerTestPB pb;
265
1
  pb.set_name("foo");
266
1
  pb.set_note("bar");
267
268
1
  std::unique_ptr<WritableFile> writer;
269
1
  ASSERT_OK(env_->NewWritableFile(path_, &writer));
270
1
  WritablePBContainerFile pb_writer(std::move(writer));
271
1
  ASSERT_OK(pb_writer.Init(pb));
272
273
11
  for (int i = 0; i < 10; i++) {
274
10
    pb.set_value(i);
275
10
    ASSERT_OK(pb_writer.Append(pb));
276
10
  }
277
1
  ASSERT_OK(pb_writer.Close());
278
279
1
  int pbs_read = 0;
280
1
  std::unique_ptr<RandomAccessFile> reader;
281
1
  ASSERT_OK(env_->NewRandomAccessFile(path_, &reader));
282
1
  ReadablePBContainerFile pb_reader(std::move(reader));
283
1
  ASSERT_OK(pb_reader.Init());
284
11
  for (int i = 0;; i++) {
285
11
    ProtoContainerTestPB read_pb;
286
11
    Status s = pb_reader.ReadNextPB(&read_pb);
287
11
    if (s.IsEndOfFile()) {
288
1
      break;
289
1
    }
290
10
    ASSERT_OK(s);
291
10
    ASSERT_EQ(pb.name(), read_pb.name());
292
10
    ASSERT_EQ(read_pb.value(), i);
293
10
    ASSERT_EQ(pb.note(), read_pb.note());
294
10
    pbs_read++;
295
10
  }
296
1
  ASSERT_EQ(10, pbs_read);
297
1
  ASSERT_OK(pb_reader.Close());
298
1
}
299
300
1
TEST_F(TestPBUtil, TestInterleavedReadWrite) {
301
1
  ProtoContainerTestPB pb;
302
1
  pb.set_name("foo");
303
1
  pb.set_note("bar");
304
305
  // Open the file for writing and reading.
306
1
  std::unique_ptr<WritableFile> writer;
307
1
  ASSERT_OK(env_->NewWritableFile(path_, &writer));
308
1
  WritablePBContainerFile pb_writer(std::move(writer));
309
1
  std::unique_ptr<RandomAccessFile> reader;
310
1
  ASSERT_OK(env_->NewRandomAccessFile(path_, &reader));
311
1
  ReadablePBContainerFile pb_reader(std::move(reader));
312
313
  // Write the header (writer) and validate it (reader).
314
1
  ASSERT_OK(pb_writer.Init(pb));
315
1
  ASSERT_OK(pb_reader.Init());
316
317
11
  for (int i = 0; i < 10; i++) {
318
    // Write a message and read it back.
319
10
    pb.set_value(i);
320
10
    ASSERT_OK(pb_writer.Append(pb));
321
10
    ProtoContainerTestPB read_pb;
322
10
    ASSERT_OK(pb_reader.ReadNextPB(&read_pb));
323
10
    ASSERT_EQ(pb.name(), read_pb.name());
324
10
    ASSERT_EQ(read_pb.value(), i);
325
10
    ASSERT_EQ(pb.note(), read_pb.note());
326
10
  }
327
328
  // After closing the writer, the reader should be out of data.
329
1
  ASSERT_OK(pb_writer.Close());
330
1
  ASSERT_TRUE(pb_reader.ReadNextPB(nullptr).IsEndOfFile());
331
1
  ASSERT_OK(pb_reader.Close());
332
1
}
333
334
1
TEST_F(TestPBUtil, TestPopulateDescriptorSet) {
335
1
  {
336
    // No dependencies --> just one proto.
337
1
    ProtoContainerTestPB pb;
338
1
    FileDescriptorSet protos;
339
1
    WritablePBContainerFile::PopulateDescriptorSet(
340
1
        pb.GetDescriptor()->file(), &protos);
341
1
    ASSERT_EQ(1, protos.file_size());
342
1
  }
343
1
  {
344
    // One direct dependency --> two protos.
345
1
    ProtoContainerTest2PB pb;
346
1
    FileDescriptorSet protos;
347
1
    WritablePBContainerFile::PopulateDescriptorSet(
348
1
        pb.GetDescriptor()->file(), &protos);
349
1
    ASSERT_EQ(2, protos.file_size());
350
1
  }
351
1
  {
352
    // One direct and one indirect dependency --> three protos.
353
1
    ProtoContainerTest3PB pb;
354
1
    FileDescriptorSet protos;
355
1
    WritablePBContainerFile::PopulateDescriptorSet(
356
1
        pb.GetDescriptor()->file(), &protos);
357
1
    ASSERT_EQ(3, protos.file_size());
358
1
  }
359
1
}
360
361
void TestPBUtil::DumpPBCToString(const string& path, bool oneline_output,
362
2
                                 string* ret) {
363
2
  std::unique_ptr<RandomAccessFile> reader;
364
2
  ASSERT_OK(env_->NewRandomAccessFile(path, &reader));
365
2
  ReadablePBContainerFile pb_reader(std::move(reader));
366
2
  ASSERT_OK(pb_reader.Init());
367
2
  ostringstream oss;
368
2
  ASSERT_OK(pb_reader.Dump(&oss, oneline_output));
369
2
  ASSERT_OK(pb_reader.Close());
370
2
  *ret = oss.str();
371
2
}
372
373
1
TEST_F(TestPBUtil, TestDumpPBContainer) {
374
1
  const char* kExpectedOutput =
375
1
      "yb.ProtoContainerTest3PB 0\n"
376
1
      "-------\n"
377
1
      "record_one {\n"
378
1
      "  name: \"foo\"\n"
379
1
      "  value: 0\n"
380
1
      "}\n"
381
1
      "record_two {\n"
382
1
      "  record {\n"
383
1
      "    name: \"foo\"\n"
384
1
      "    value: 0\n"
385
1
      "  }\n"
386
1
      "}\n"
387
1
      "\n"
388
1
      "yb.ProtoContainerTest3PB 1\n"
389
1
      "-------\n"
390
1
      "record_one {\n"
391
1
      "  name: \"foo\"\n"
392
1
      "  value: 1\n"
393
1
      "}\n"
394
1
      "record_two {\n"
395
1
      "  record {\n"
396
1
      "    name: \"foo\"\n"
397
1
      "    value: 2\n"
398
1
      "  }\n"
399
1
      "}\n\n";
400
401
1
  const char* kExpectedOutputShort =
402
1
    "0\trecord_one { name: \"foo\" value: 0 } record_two { record { name: \"foo\" value: 0 } }\n"
403
1
    "1\trecord_one { name: \"foo\" value: 1 } record_two { record { name: \"foo\" value: 2 } }\n";
404
405
1
  ProtoContainerTest3PB pb;
406
1
  pb.mutable_record_one()->set_name("foo");
407
1
  pb.mutable_record_two()->mutable_record()->set_name("foo");
408
409
1
  std::unique_ptr<WritableFile> writer;
410
1
  ASSERT_OK(env_->NewWritableFile(path_, &writer));
411
1
  WritablePBContainerFile pb_writer(std::move(writer));
412
1
  ASSERT_OK(pb_writer.Init(pb));
413
414
3
  for (int i = 0; i < 2; i++) {
415
2
    pb.mutable_record_one()->set_value(i);
416
2
    pb.mutable_record_two()->mutable_record()->set_value(i*2);
417
2
    ASSERT_OK(pb_writer.Append(pb));
418
2
  }
419
1
  ASSERT_OK(pb_writer.Close());
420
421
1
  string output;
422
1
  DumpPBCToString(path_, false, &output);
423
1
  ASSERT_STREQ(kExpectedOutput, output.c_str());
424
425
1
  DumpPBCToString(path_, true, &output);
426
1
  ASSERT_STREQ(kExpectedOutputShort, output.c_str());
427
1
}
428
429
1
TEST_F(TestPBUtil, TestOverwriteExistingPB) {
430
1
  ASSERT_OK(CreateKnownGoodContainerFile(NO_OVERWRITE));
431
1
  ASSERT_TRUE(CreateKnownGoodContainerFile(NO_OVERWRITE).IsAlreadyPresent());
432
1
  ASSERT_OK(CreateKnownGoodContainerFile(OVERWRITE));
433
1
  ASSERT_OK(CreateKnownGoodContainerFile(OVERWRITE));
434
1
}
435
436
PB_ENUM_FORMATTERS(TestPBEnum)
437
438
1
TEST_F(TestPBUtil, TestEnumToString) {
439
1
  {
440
1
    std::stringstream ss;
441
1
    ss << TestPBEnum::FOO;
442
1
    ASSERT_EQ("FOO", ss.str());
443
1
    ASSERT_EQ("FOO", PBEnumToString(TestPBEnum::FOO));
444
1
    ASSERT_EQ("FOO", ToString(TestPBEnum::FOO));
445
1
  }
446
447
1
#if !defined(ADDRESS_SANITIZER)
448
1
  {
449
1
    std::stringstream ss;
450
1
    const auto kInvalidValue = static_cast<TestPBEnum>(10);
451
1
    ss << kInvalidValue;
452
1
    const char* kExpectedStr = "<unknown TestPBEnum : 10>";
453
1
    ASSERT_EQ(kExpectedStr, ss.str());
454
1
    ASSERT_EQ(kExpectedStr, PBEnumToString(kInvalidValue));
455
1
    ASSERT_EQ(kExpectedStr, ToString(kInvalidValue));
456
1
  }
457
1
#endif
458
1
}
459
460
1
TEST_F(TestPBUtil, TestPBRequiredToRepeated) {
461
  // Write the file with required fields.
462
1
  {
463
1
    TestObjectRequiredPB pb;
464
1
    pb.set_string1(kTestString + "1");
465
1
    pb.set_string2(kTestString + "2");
466
1
    pb.mutable_record()->set_text(kTestString);
467
1
    ASSERT_OK(WritePBContainerToPath(env_.get(), path_, pb, OVERWRITE, SYNC));
468
1
  }
469
470
  // Read it back as repeated fields, should validate and contain the expected values.
471
1
  TestObjectRepeatedPB pb;
472
1
  ASSERT_OK(ReadPBContainerFromPath(env_.get(), path_, &pb));
473
1
  ASSERT_EQ(1, pb.string1_size());
474
1
  ASSERT_EQ(1, pb.string2_size());
475
1
  ASSERT_EQ(1, pb.record_size());
476
1
  ASSERT_EQ(kTestString + "1", pb.string1()[0]);
477
1
  ASSERT_EQ(kTestString + "2", pb.string2()[0]);
478
1
  ASSERT_EQ(kTestString, pb.record()[0].text()[0]);
479
480
  // Delete the file.
481
1
  ASSERT_OK(env_->DeleteFile(path_));
482
1
}
483
484
} // namespace pb_util
485
} // namespace yb