proxygen
RecordIOTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2013-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <folly/io/RecordIO.h>
18 
19 #include <sys/types.h>
20 
21 #include <random>
22 
23 #include <glog/logging.h>
24 
25 #include <folly/Conv.h>
26 #include <folly/FBString.h>
27 #include <folly/Random.h>
29 #include <folly/io/IOBufQueue.h>
33 
34 DEFINE_int32(random_seed, folly::randomNumberSeed(), "random seed");
35 
36 namespace folly {
37 namespace test {
38 
39 namespace {
40 // shortcut
41 StringPiece sp(ByteRange br) {
42  return StringPiece(br);
43 }
44 
45 template <class T>
46 std::unique_ptr<IOBuf> iobufs(std::initializer_list<T> ranges) {
47  IOBufQueue queue;
48  for (auto& range : ranges) {
49  StringPiece r(range);
50  queue.append(IOBuf::wrapBuffer(r.data(), r.size()));
51  }
52  return queue.move();
53 }
54 
55 } // namespace
56 
57 TEST(RecordIOTest, Simple) {
58  TemporaryFile file;
59  {
60  RecordIOWriter writer(File(file.fd()));
61  writer.write(iobufs({"hello ", "world"}));
62  writer.write(iobufs({"goodbye"}));
63  }
64  {
65  RecordIOReader reader(File(file.fd()));
66  auto it = reader.begin();
67  ASSERT_FALSE(it == reader.end());
68  EXPECT_EQ("hello world", sp((it++)->first));
69  ASSERT_FALSE(it == reader.end());
70  EXPECT_EQ("goodbye", sp((it++)->first));
71  EXPECT_TRUE(it == reader.end());
72  }
73  {
74  RecordIOWriter writer(File(file.fd()));
75  writer.write(iobufs({"meow"}));
76  writer.write(iobufs({"woof"}));
77  }
78  {
79  RecordIOReader reader(File(file.fd()));
80  auto it = reader.begin();
81  ASSERT_FALSE(it == reader.end());
82  EXPECT_EQ("hello world", sp((it++)->first));
83  ASSERT_FALSE(it == reader.end());
84  EXPECT_EQ("goodbye", sp((it++)->first));
85  ASSERT_FALSE(it == reader.end());
86  EXPECT_EQ("meow", sp((it++)->first));
87  ASSERT_FALSE(it == reader.end());
88  EXPECT_EQ("woof", sp((it++)->first));
89  EXPECT_TRUE(it == reader.end());
90  }
91 }
92 
93 TEST(RecordIOTest, SmallRecords) {
94  constexpr size_t kSize = 10;
95  char tmp[kSize];
96  memset(tmp, 'x', kSize);
97  TemporaryFile file;
98  {
99  RecordIOWriter writer(File(file.fd()));
100  for (size_t i = 0; i < kSize; ++i) { // record of size 0 should be ignored
101  writer.write(IOBuf::wrapBuffer(tmp, i));
102  }
103  }
104  {
105  RecordIOReader reader(File(file.fd()));
106  auto it = reader.begin();
107  for (size_t i = 1; i < kSize; ++i) {
108  ASSERT_FALSE(it == reader.end());
109  EXPECT_EQ(StringPiece(tmp, i), sp((it++)->first));
110  }
111  EXPECT_TRUE(it == reader.end());
112  }
113 }
114 
115 TEST(RecordIOTest, MultipleFileIds) {
116  TemporaryFile file;
117  {
118  RecordIOWriter writer(File(file.fd()), 1);
119  writer.write(iobufs({"hello"}));
120  }
121  {
122  RecordIOWriter writer(File(file.fd()), 2);
123  writer.write(iobufs({"world"}));
124  }
125  {
126  RecordIOWriter writer(File(file.fd()), 1);
127  writer.write(iobufs({"goodbye"}));
128  }
129  {
130  RecordIOReader reader(File(file.fd()), 0); // return all
131  auto it = reader.begin();
132  ASSERT_FALSE(it == reader.end());
133  EXPECT_EQ("hello", sp((it++)->first));
134  ASSERT_FALSE(it == reader.end());
135  EXPECT_EQ("world", sp((it++)->first));
136  ASSERT_FALSE(it == reader.end());
137  EXPECT_EQ("goodbye", sp((it++)->first));
138  EXPECT_TRUE(it == reader.end());
139  }
140  {
141  RecordIOReader reader(File(file.fd()), 1);
142  auto it = reader.begin();
143  ASSERT_FALSE(it == reader.end());
144  EXPECT_EQ("hello", sp((it++)->first));
145  ASSERT_FALSE(it == reader.end());
146  EXPECT_EQ("goodbye", sp((it++)->first));
147  EXPECT_TRUE(it == reader.end());
148  }
149  {
150  RecordIOReader reader(File(file.fd()), 2);
151  auto it = reader.begin();
152  ASSERT_FALSE(it == reader.end());
153  EXPECT_EQ("world", sp((it++)->first));
154  EXPECT_TRUE(it == reader.end());
155  }
156  {
157  RecordIOReader reader(File(file.fd()), 3);
158  auto it = reader.begin();
159  EXPECT_TRUE(it == reader.end());
160  }
161 }
162 
163 TEST(RecordIOTest, ExtraMagic) {
164  TemporaryFile file;
165  {
166  RecordIOWriter writer(File(file.fd()));
167  writer.write(iobufs({"hello"}));
168  }
170  EXPECT_EQ(0, lseek(file.fd(), 0, SEEK_SET));
171  EXPECT_EQ(sizeof(buf), read(file.fd(), buf, sizeof(buf)));
172  // Append an extra magic
174  EXPECT_EQ(sizeof(magic), write(file.fd(), &magic, sizeof(magic)));
175  // and an extra record
176  EXPECT_EQ(sizeof(buf), write(file.fd(), buf, sizeof(buf)));
177  {
178  RecordIOReader reader(File(file.fd()));
179  auto it = reader.begin();
180  ASSERT_FALSE(it == reader.end());
181  EXPECT_EQ("hello", sp((it++)->first));
182  ASSERT_FALSE(it == reader.end());
183  EXPECT_EQ("hello", sp((it++)->first));
184  EXPECT_TRUE(it == reader.end());
185  }
186 }
187 
188 namespace {
189 void corrupt(int fd, off_t pos) {
190  uint8_t val = 0;
191  EXPECT_EQ(1, pread(fd, &val, 1, pos));
192  ++val;
193  EXPECT_EQ(1, pwrite(fd, &val, 1, pos));
194 }
195 } // namespace
196 
197 TEST(RecordIOTest, Randomized) {
198  SCOPED_TRACE(to<std::string>("Random seed is ", FLAGS_random_seed));
199  std::mt19937 rnd(FLAGS_random_seed);
200 
201  size_t recordCount = std::uniform_int_distribution<uint32_t>(30, 300)(rnd);
202 
203  std::uniform_int_distribution<uint32_t> recordSizeDist(1, 3 << 16);
204  std::uniform_int_distribution<uint32_t> charDist(0, 255);
205  std::uniform_int_distribution<uint32_t> junkDist(0, 1 << 20);
206  // corrupt 1/5 of all records
207  std::uniform_int_distribution<uint32_t> corruptDist(0, 4);
208 
209  std::vector<std::pair<fbstring, off_t>> records;
210  std::vector<off_t> corruptPositions;
211  records.reserve(recordCount);
212  TemporaryFile file;
213 
214  fbstring record;
215  // Recreate the writer multiple times so we test that we create a
216  // continuous stream
217  for (size_t i = 0; i < 3; ++i) {
218  RecordIOWriter writer(File(file.fd()));
219  for (size_t j = 0; j < recordCount; ++j) {
220  off_t beginPos = writer.filePos();
221  record.clear();
222  size_t recordSize = recordSizeDist(rnd);
223  record.reserve(recordSize);
224  for (size_t k = 0; k < recordSize; ++k) {
225  record.push_back(charDist(rnd));
226  }
227  writer.write(iobufs({record}));
228 
229  bool corrupt = (corruptDist(rnd) == 0);
230  if (corrupt) {
231  // Corrupt one random byte in the record (including header)
232  std::uniform_int_distribution<uint32_t> corruptByteDist(
233  0, recordSize + recordio_helpers::headerSize() - 1);
234  off_t corruptRel = corruptByteDist(rnd);
235  VLOG(1) << "n=" << records.size() << " bpos=" << beginPos
236  << " rsize=" << record.size() << " corrupt rel=" << corruptRel
237  << " abs=" << beginPos + corruptRel;
238  corruptPositions.push_back(beginPos + corruptRel);
239  } else {
240  VLOG(2) << "n=" << records.size() << " bpos=" << beginPos
241  << " rsize=" << record.size() << " good";
242  records.emplace_back(std::move(record), beginPos);
243  }
244  }
245  VLOG(1) << "n=" << records.size() << " close abs=" << writer.filePos();
246  }
247 
248  for (auto& pos : corruptPositions) {
249  corrupt(file.fd(), pos);
250  }
251 
252  {
253  size_t i = 0;
254  RecordIOReader reader(File(file.fd()));
255  for (auto& r : reader) {
256  SCOPED_TRACE(i);
257  ASSERT_LT(i, records.size());
258  EXPECT_EQ(records[i].first, sp(r.first));
259  EXPECT_EQ(records[i].second, r.second);
260  ++i;
261  }
262  EXPECT_EQ(records.size(), i);
263  }
264 }
265 } // namespace test
266 } // namespace folly
267 
268 int main(int argc, char* argv[]) {
269  testing::InitGoogleTest(&argc, argv);
270  gflags::ParseCommandLineFlags(&argc, &argv, true);
271  return RUN_ALL_TESTS();
272 }
size_type size() const
Definition: FBString.h:1337
DEFINE_int32(random_seed, folly::randomNumberSeed(),"random seed")
constexpr size_t headerSize()
Definition: RecordIO-inl.h:101
void write(const T &in, folly::io::Appender &appender)
Definition: Types-inl.h:112
#define ASSERT_LT(val1, val2)
Definition: gtest.h:1968
static std::unique_ptr< IOBuf > wrapBuffer(const void *buf, std::size_t capacity)
Definition: IOBuf.cpp:353
int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_
Definition: gtest.h:2232
Iterator begin() const
Definition: RecordIO-inl.h:60
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
off_t filePos() const
Definition: RecordIO.h:72
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
double val
Definition: String.cpp:273
#define SCOPED_TRACE(message)
Definition: gtest.h:2115
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
Iterator end() const
Definition: RecordIO-inl.h:66
void reserve(size_type res_arg=0)
Definition: FBString.h:1355
char ** argv
size_t read(T &out, folly::io::Cursor &cursor)
Definition: Types-inl.h:258
constexpr Range< Iter > range(Iter first, Iter last)
Definition: Range.h:1114
void push_back(const value_type c)
Definition: FBString.h:1437
TEST(ProgramOptionsTest, Errors)
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
Range< const unsigned char * > ByteRange
Definition: Range.h:1163
int main(int argc, char *argv[])
GTEST_API_ void InitGoogleTest(int *argc, char **argv)
Definition: gtest.cc:5370
#define ASSERT_FALSE(condition)
Definition: gtest.h:1868
void write(std::unique_ptr< IOBuf > buf)
Definition: RecordIO.cpp:48
Range< const char * > StringPiece
fbstring records
KeyT k
uint32_t randomNumberSeed()
Definition: Random.h:367
constexpr detail::First first
Definition: Base-inl.h:2553