RecordIOTest.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. #include <folly/io/RecordIO.h>
  17. #include <sys/types.h>
  18. #include <random>
  19. #include <glog/logging.h>
  20. #include <folly/Conv.h>
  21. #include <folly/FBString.h>
  22. #include <folly/Random.h>
  23. #include <folly/experimental/TestUtil.h>
  24. #include <folly/io/IOBufQueue.h>
  25. #include <folly/portability/GFlags.h>
  26. #include <folly/portability/GTest.h>
  27. #include <folly/portability/Unistd.h>
  28. DEFINE_int32(random_seed, folly::randomNumberSeed(), "random seed");
  29. namespace folly {
  30. namespace test {
  31. namespace {
  32. // shortcut
  33. StringPiece sp(ByteRange br) {
  34. return StringPiece(br);
  35. }
  36. template <class T>
  37. std::unique_ptr<IOBuf> iobufs(std::initializer_list<T> ranges) {
  38. IOBufQueue queue;
  39. for (auto& range : ranges) {
  40. StringPiece r(range);
  41. queue.append(IOBuf::wrapBuffer(r.data(), r.size()));
  42. }
  43. return queue.move();
  44. }
  45. } // namespace
  46. TEST(RecordIOTest, Simple) {
  47. TemporaryFile file;
  48. {
  49. RecordIOWriter writer(File(file.fd()));
  50. writer.write(iobufs({"hello ", "world"}));
  51. writer.write(iobufs({"goodbye"}));
  52. }
  53. {
  54. RecordIOReader reader(File(file.fd()));
  55. auto it = reader.begin();
  56. ASSERT_FALSE(it == reader.end());
  57. EXPECT_EQ("hello world", sp((it++)->first));
  58. ASSERT_FALSE(it == reader.end());
  59. EXPECT_EQ("goodbye", sp((it++)->first));
  60. EXPECT_TRUE(it == reader.end());
  61. }
  62. {
  63. RecordIOWriter writer(File(file.fd()));
  64. writer.write(iobufs({"meow"}));
  65. writer.write(iobufs({"woof"}));
  66. }
  67. {
  68. RecordIOReader reader(File(file.fd()));
  69. auto it = reader.begin();
  70. ASSERT_FALSE(it == reader.end());
  71. EXPECT_EQ("hello world", sp((it++)->first));
  72. ASSERT_FALSE(it == reader.end());
  73. EXPECT_EQ("goodbye", sp((it++)->first));
  74. ASSERT_FALSE(it == reader.end());
  75. EXPECT_EQ("meow", sp((it++)->first));
  76. ASSERT_FALSE(it == reader.end());
  77. EXPECT_EQ("woof", sp((it++)->first));
  78. EXPECT_TRUE(it == reader.end());
  79. }
  80. }
  81. TEST(RecordIOTest, SmallRecords) {
  82. constexpr size_t kSize = 10;
  83. char tmp[kSize];
  84. memset(tmp, 'x', kSize);
  85. TemporaryFile file;
  86. {
  87. RecordIOWriter writer(File(file.fd()));
  88. for (size_t i = 0; i < kSize; ++i) { // record of size 0 should be ignored
  89. writer.write(IOBuf::wrapBuffer(tmp, i));
  90. }
  91. }
  92. {
  93. RecordIOReader reader(File(file.fd()));
  94. auto it = reader.begin();
  95. for (size_t i = 1; i < kSize; ++i) {
  96. ASSERT_FALSE(it == reader.end());
  97. EXPECT_EQ(StringPiece(tmp, i), sp((it++)->first));
  98. }
  99. EXPECT_TRUE(it == reader.end());
  100. }
  101. }
  102. TEST(RecordIOTest, MultipleFileIds) {
  103. TemporaryFile file;
  104. {
  105. RecordIOWriter writer(File(file.fd()), 1);
  106. writer.write(iobufs({"hello"}));
  107. }
  108. {
  109. RecordIOWriter writer(File(file.fd()), 2);
  110. writer.write(iobufs({"world"}));
  111. }
  112. {
  113. RecordIOWriter writer(File(file.fd()), 1);
  114. writer.write(iobufs({"goodbye"}));
  115. }
  116. {
  117. RecordIOReader reader(File(file.fd()), 0); // return all
  118. auto it = reader.begin();
  119. ASSERT_FALSE(it == reader.end());
  120. EXPECT_EQ("hello", sp((it++)->first));
  121. ASSERT_FALSE(it == reader.end());
  122. EXPECT_EQ("world", sp((it++)->first));
  123. ASSERT_FALSE(it == reader.end());
  124. EXPECT_EQ("goodbye", sp((it++)->first));
  125. EXPECT_TRUE(it == reader.end());
  126. }
  127. {
  128. RecordIOReader reader(File(file.fd()), 1);
  129. auto it = reader.begin();
  130. ASSERT_FALSE(it == reader.end());
  131. EXPECT_EQ("hello", sp((it++)->first));
  132. ASSERT_FALSE(it == reader.end());
  133. EXPECT_EQ("goodbye", sp((it++)->first));
  134. EXPECT_TRUE(it == reader.end());
  135. }
  136. {
  137. RecordIOReader reader(File(file.fd()), 2);
  138. auto it = reader.begin();
  139. ASSERT_FALSE(it == reader.end());
  140. EXPECT_EQ("world", sp((it++)->first));
  141. EXPECT_TRUE(it == reader.end());
  142. }
  143. {
  144. RecordIOReader reader(File(file.fd()), 3);
  145. auto it = reader.begin();
  146. EXPECT_TRUE(it == reader.end());
  147. }
  148. }
  149. TEST(RecordIOTest, ExtraMagic) {
  150. TemporaryFile file;
  151. {
  152. RecordIOWriter writer(File(file.fd()));
  153. writer.write(iobufs({"hello"}));
  154. }
  155. uint8_t buf[recordio_helpers::headerSize() + 5];
  156. EXPECT_EQ(0, lseek(file.fd(), 0, SEEK_SET));
  157. EXPECT_EQ(sizeof(buf), read(file.fd(), buf, sizeof(buf)));
  158. // Append an extra magic
  159. const uint32_t magic = recordio_helpers::recordio_detail::Header::kMagic;
  160. EXPECT_EQ(sizeof(magic), write(file.fd(), &magic, sizeof(magic)));
  161. // and an extra record
  162. EXPECT_EQ(sizeof(buf), write(file.fd(), buf, sizeof(buf)));
  163. {
  164. RecordIOReader reader(File(file.fd()));
  165. auto it = reader.begin();
  166. ASSERT_FALSE(it == reader.end());
  167. EXPECT_EQ("hello", sp((it++)->first));
  168. ASSERT_FALSE(it == reader.end());
  169. EXPECT_EQ("hello", sp((it++)->first));
  170. EXPECT_TRUE(it == reader.end());
  171. }
  172. }
  173. namespace {
  174. void corrupt(int fd, off_t pos) {
  175. uint8_t val = 0;
  176. EXPECT_EQ(1, pread(fd, &val, 1, pos));
  177. ++val;
  178. EXPECT_EQ(1, pwrite(fd, &val, 1, pos));
  179. }
  180. } // namespace
  181. TEST(RecordIOTest, Randomized) {
  182. SCOPED_TRACE(to<std::string>("Random seed is ", FLAGS_random_seed));
  183. std::mt19937 rnd(FLAGS_random_seed);
  184. size_t recordCount = std::uniform_int_distribution<uint32_t>(30, 300)(rnd);
  185. std::uniform_int_distribution<uint32_t> recordSizeDist(1, 3 << 16);
  186. std::uniform_int_distribution<uint32_t> charDist(0, 255);
  187. std::uniform_int_distribution<uint32_t> junkDist(0, 1 << 20);
  188. // corrupt 1/5 of all records
  189. std::uniform_int_distribution<uint32_t> corruptDist(0, 4);
  190. std::vector<std::pair<fbstring, off_t>> records;
  191. std::vector<off_t> corruptPositions;
  192. records.reserve(recordCount);
  193. TemporaryFile file;
  194. fbstring record;
  195. // Recreate the writer multiple times so we test that we create a
  196. // continuous stream
  197. for (size_t i = 0; i < 3; ++i) {
  198. RecordIOWriter writer(File(file.fd()));
  199. for (size_t j = 0; j < recordCount; ++j) {
  200. off_t beginPos = writer.filePos();
  201. record.clear();
  202. size_t recordSize = recordSizeDist(rnd);
  203. record.reserve(recordSize);
  204. for (size_t k = 0; k < recordSize; ++k) {
  205. record.push_back(charDist(rnd));
  206. }
  207. writer.write(iobufs({record}));
  208. bool corrupt = (corruptDist(rnd) == 0);
  209. if (corrupt) {
  210. // Corrupt one random byte in the record (including header)
  211. std::uniform_int_distribution<uint32_t> corruptByteDist(
  212. 0, recordSize + recordio_helpers::headerSize() - 1);
  213. off_t corruptRel = corruptByteDist(rnd);
  214. VLOG(1) << "n=" << records.size() << " bpos=" << beginPos
  215. << " rsize=" << record.size() << " corrupt rel=" << corruptRel
  216. << " abs=" << beginPos + corruptRel;
  217. corruptPositions.push_back(beginPos + corruptRel);
  218. } else {
  219. VLOG(2) << "n=" << records.size() << " bpos=" << beginPos
  220. << " rsize=" << record.size() << " good";
  221. records.emplace_back(std::move(record), beginPos);
  222. }
  223. }
  224. VLOG(1) << "n=" << records.size() << " close abs=" << writer.filePos();
  225. }
  226. for (auto& pos : corruptPositions) {
  227. corrupt(file.fd(), pos);
  228. }
  229. {
  230. size_t i = 0;
  231. RecordIOReader reader(File(file.fd()));
  232. for (auto& r : reader) {
  233. SCOPED_TRACE(i);
  234. ASSERT_LT(i, records.size());
  235. EXPECT_EQ(records[i].first, sp(r.first));
  236. EXPECT_EQ(records[i].second, r.second);
  237. ++i;
  238. }
  239. EXPECT_EQ(records.size(), i);
  240. }
  241. }
  242. } // namespace test
  243. } // namespace folly
  244. int main(int argc, char* argv[]) {
  245. testing::InitGoogleTest(&argc, argv);
  246. gflags::ParseCommandLineFlags(&argc, &argv, true);
  247. return RUN_ALL_TESTS();
  248. }