Switch uncompressedLength to an Optional<uint64_t>
[folly.git] / folly / io / test / CompressionTest.cpp
1 /*
2  * Copyright 2017 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/Compression.h>
18
19 #include <random>
20 #include <set>
21 #include <thread>
22 #include <unordered_map>
23
24 #include <boost/noncopyable.hpp>
25 #include <glog/logging.h>
26
27 #include <folly/Benchmark.h>
28 #include <folly/Hash.h>
29 #include <folly/Memory.h>
30 #include <folly/Random.h>
31 #include <folly/Varint.h>
32 #include <folly/io/IOBufQueue.h>
33 #include <folly/portability/GTest.h>
34
35 namespace folly { namespace io { namespace test {
36
37 class DataHolder : private boost::noncopyable {
38  public:
39   uint64_t hash(size_t size) const;
40   ByteRange data(size_t size) const;
41
42  protected:
43   explicit DataHolder(size_t sizeLog2);
44   const size_t size_;
45   std::unique_ptr<uint8_t[]> data_;
46   mutable std::unordered_map<uint64_t, uint64_t> hashCache_;
47 };
48
49 DataHolder::DataHolder(size_t sizeLog2)
50   : size_(size_t(1) << sizeLog2),
51     data_(new uint8_t[size_]) {
52 }
53
54 uint64_t DataHolder::hash(size_t size) const {
55   CHECK_LE(size, size_);
56   auto p = hashCache_.find(size);
57   if (p != hashCache_.end()) {
58     return p->second;
59   }
60
61   uint64_t h = folly::hash::fnv64_buf(data_.get(), size);
62   hashCache_[size] = h;
63   return h;
64 }
65
66 ByteRange DataHolder::data(size_t size) const {
67   CHECK_LE(size, size_);
68   return ByteRange(data_.get(), size);
69 }
70
71 uint64_t hashIOBuf(const IOBuf* buf) {
72   uint64_t h = folly::hash::FNV_64_HASH_START;
73   for (auto& range : *buf) {
74     h = folly::hash::fnv64_buf(range.data(), range.size(), h);
75   }
76   return h;
77 }
78
79 class RandomDataHolder : public DataHolder {
80  public:
81   explicit RandomDataHolder(size_t sizeLog2);
82 };
83
84 RandomDataHolder::RandomDataHolder(size_t sizeLog2)
85   : DataHolder(sizeLog2) {
86   constexpr size_t numThreadsLog2 = 3;
87   constexpr size_t numThreads = size_t(1) << numThreadsLog2;
88
89   uint32_t seed = randomNumberSeed();
90
91   std::vector<std::thread> threads;
92   threads.reserve(numThreads);
93   for (size_t t = 0; t < numThreads; ++t) {
94     threads.emplace_back(
95         [this, seed, t, numThreadsLog2, sizeLog2] () {
96           std::mt19937 rng(seed + t);
97           size_t countLog2 = sizeLog2 - numThreadsLog2;
98           size_t start = size_t(t) << countLog2;
99           for (size_t i = 0; i < countLog2; ++i) {
100             this->data_[start + i] = rng();
101           }
102         });
103   }
104
105   for (auto& t : threads) {
106     t.join();
107   }
108 }
109
110 class ConstantDataHolder : public DataHolder {
111  public:
112   explicit ConstantDataHolder(size_t sizeLog2);
113 };
114
115 ConstantDataHolder::ConstantDataHolder(size_t sizeLog2)
116   : DataHolder(sizeLog2) {
117   memset(data_.get(), 'a', size_);
118 }
119
120 constexpr size_t dataSizeLog2 = 27;  // 128MiB
121 RandomDataHolder randomDataHolder(dataSizeLog2);
122 ConstantDataHolder constantDataHolder(dataSizeLog2);
123
124 // The intersection of the provided codecs & those that are compiled in.
125 static std::vector<CodecType> supportedCodecs(std::vector<CodecType> const& v) {
126   std::vector<CodecType> supported;
127
128   std::copy_if(
129       std::begin(v),
130       std::end(v),
131       std::back_inserter(supported),
132       hasCodec);
133
134   return supported;
135 }
136
137 // All compiled-in compression codecs.
138 static std::vector<CodecType> availableCodecs() {
139   std::vector<CodecType> codecs;
140
141   for (size_t i = 0; i < static_cast<size_t>(CodecType::NUM_CODEC_TYPES); ++i) {
142     auto type = static_cast<CodecType>(i);
143     if (hasCodec(type)) {
144       codecs.push_back(type);
145     }
146   }
147
148   return codecs;
149 }
150
151 TEST(CompressionTestNeedsUncompressedLength, Simple) {
152   static const struct { CodecType type; bool needsUncompressedLength; }
153     expectations[] = {
154       { CodecType::NO_COMPRESSION, false },
155       { CodecType::LZ4, true },
156       { CodecType::SNAPPY, false },
157       { CodecType::ZLIB, false },
158       { CodecType::LZ4_VARINT_SIZE, false },
159       { CodecType::LZMA2, false },
160       { CodecType::LZMA2_VARINT_SIZE, false },
161       { CodecType::ZSTD, false },
162       { CodecType::GZIP, false },
163       { CodecType::LZ4_FRAME, false },
164       { CodecType::BZIP2, false },
165     };
166
167   for (auto const& test : expectations) {
168     if (hasCodec(test.type)) {
169       EXPECT_EQ(getCodec(test.type)->needsUncompressedLength(),
170                 test.needsUncompressedLength);
171     }
172   }
173 }
174
175 class CompressionTest
176     : public testing::TestWithParam<std::tr1::tuple<int, int, CodecType>> {
177  protected:
178   void SetUp() override {
179     auto tup = GetParam();
180     uncompressedLength_ = uint64_t(1) << std::tr1::get<0>(tup);
181     chunks_ = std::tr1::get<1>(tup);
182     codec_ = getCodec(std::tr1::get<2>(tup));
183   }
184
185   void runSimpleIOBufTest(const DataHolder& dh);
186
187   void runSimpleStringTest(const DataHolder& dh);
188
189  private:
190   std::unique_ptr<IOBuf> split(std::unique_ptr<IOBuf> data) const;
191
192   uint64_t uncompressedLength_;
193   size_t chunks_;
194   std::unique_ptr<Codec> codec_;
195 };
196
197 void CompressionTest::runSimpleIOBufTest(const DataHolder& dh) {
198   const auto original = split(IOBuf::wrapBuffer(dh.data(uncompressedLength_)));
199   const auto compressed = split(codec_->compress(original.get()));
200   if (!codec_->needsUncompressedLength()) {
201     auto uncompressed = codec_->uncompress(compressed.get());
202     EXPECT_EQ(uncompressedLength_, uncompressed->computeChainDataLength());
203     EXPECT_EQ(dh.hash(uncompressedLength_), hashIOBuf(uncompressed.get()));
204   }
205   {
206     auto uncompressed = codec_->uncompress(compressed.get(),
207                                            uncompressedLength_);
208     EXPECT_EQ(uncompressedLength_, uncompressed->computeChainDataLength());
209     EXPECT_EQ(dh.hash(uncompressedLength_), hashIOBuf(uncompressed.get()));
210   }
211 }
212
213 void CompressionTest::runSimpleStringTest(const DataHolder& dh) {
214   const auto original = std::string(
215       reinterpret_cast<const char*>(dh.data(uncompressedLength_).data()),
216       uncompressedLength_);
217   const auto compressed = codec_->compress(original);
218   if (!codec_->needsUncompressedLength()) {
219     auto uncompressed = codec_->uncompress(compressed);
220     EXPECT_EQ(uncompressedLength_, uncompressed.length());
221     EXPECT_EQ(uncompressed, original);
222   }
223   {
224     auto uncompressed = codec_->uncompress(compressed, uncompressedLength_);
225     EXPECT_EQ(uncompressedLength_, uncompressed.length());
226     EXPECT_EQ(uncompressed, original);
227   }
228 }
229
230 // Uniformly split data into (potentially empty) chunks.
231 std::unique_ptr<IOBuf> CompressionTest::split(
232     std::unique_ptr<IOBuf> data) const {
233   if (data->isChained()) {
234     data->coalesce();
235   }
236
237   const size_t size = data->computeChainDataLength();
238
239   std::multiset<size_t> splits;
240   for (size_t i = 1; i < chunks_; ++i) {
241     splits.insert(Random::rand64(size));
242   }
243
244   folly::IOBufQueue result;
245
246   size_t offset = 0;
247   for (size_t split : splits) {
248     result.append(IOBuf::copyBuffer(data->data() + offset, split - offset));
249     offset = split;
250   }
251   result.append(IOBuf::copyBuffer(data->data() + offset, size - offset));
252
253   return result.move();
254 }
255
256 TEST_P(CompressionTest, RandomData) {
257   runSimpleIOBufTest(randomDataHolder);
258 }
259
260 TEST_P(CompressionTest, ConstantData) {
261   runSimpleIOBufTest(constantDataHolder);
262 }
263
264 TEST_P(CompressionTest, RandomDataString) {
265   runSimpleStringTest(randomDataHolder);
266 }
267
268 TEST_P(CompressionTest, ConstantDataString) {
269   runSimpleStringTest(constantDataHolder);
270 }
271
272 INSTANTIATE_TEST_CASE_P(
273     CompressionTest,
274     CompressionTest,
275     testing::Combine(
276         testing::Values(0, 1, 12, 22, 25, 27),
277         testing::Values(1, 2, 3, 8, 65),
278         testing::ValuesIn(availableCodecs())));
279
280 class CompressionVarintTest
281     : public testing::TestWithParam<std::tr1::tuple<int, CodecType>> {
282  protected:
283   void SetUp() override {
284     auto tup = GetParam();
285     uncompressedLength_ = uint64_t(1) << std::tr1::get<0>(tup);
286     codec_ = getCodec(std::tr1::get<1>(tup));
287   }
288
289   void runSimpleTest(const DataHolder& dh);
290
291   uint64_t uncompressedLength_;
292   std::unique_ptr<Codec> codec_;
293 };
294
295 inline uint64_t oneBasedMsbPos(uint64_t number) {
296   uint64_t pos = 0;
297   for (; number > 0; ++pos, number >>= 1) {
298   }
299   return pos;
300 }
301
302 void CompressionVarintTest::runSimpleTest(const DataHolder& dh) {
303   auto original = IOBuf::wrapBuffer(dh.data(uncompressedLength_));
304   auto compressed = codec_->compress(original.get());
305   auto breakPoint =
306       1UL +
307       Random::rand64(
308           std::max(uint64_t(9), oneBasedMsbPos(uncompressedLength_)) / 9UL);
309   auto tinyBuf = IOBuf::copyBuffer(compressed->data(),
310                                    std::min(compressed->length(), breakPoint));
311   compressed->trimStart(breakPoint);
312   tinyBuf->prependChain(std::move(compressed));
313   compressed = std::move(tinyBuf);
314
315   auto uncompressed = codec_->uncompress(compressed.get());
316
317   EXPECT_EQ(uncompressedLength_, uncompressed->computeChainDataLength());
318   EXPECT_EQ(dh.hash(uncompressedLength_), hashIOBuf(uncompressed.get()));
319 }
320
321 TEST_P(CompressionVarintTest, RandomData) {
322   runSimpleTest(randomDataHolder);
323 }
324
325 TEST_P(CompressionVarintTest, ConstantData) {
326   runSimpleTest(constantDataHolder);
327 }
328
329 INSTANTIATE_TEST_CASE_P(
330     CompressionVarintTest,
331     CompressionVarintTest,
332     testing::Combine(
333         testing::Values(0, 1, 12, 22, 25, 27),
334         testing::ValuesIn(supportedCodecs({
335             CodecType::LZ4_VARINT_SIZE,
336             CodecType::LZMA2_VARINT_SIZE,
337             }))));
338
339 class CompressionCorruptionTest : public testing::TestWithParam<CodecType> {
340  protected:
341   void SetUp() override { codec_ = getCodec(GetParam()); }
342
343   void runSimpleTest(const DataHolder& dh);
344
345   std::unique_ptr<Codec> codec_;
346 };
347
348 void CompressionCorruptionTest::runSimpleTest(const DataHolder& dh) {
349   constexpr uint64_t uncompressedLength = 42;
350   auto original = IOBuf::wrapBuffer(dh.data(uncompressedLength));
351   auto compressed = codec_->compress(original.get());
352
353   if (!codec_->needsUncompressedLength()) {
354     auto uncompressed = codec_->uncompress(compressed.get());
355     EXPECT_EQ(uncompressedLength, uncompressed->computeChainDataLength());
356     EXPECT_EQ(dh.hash(uncompressedLength), hashIOBuf(uncompressed.get()));
357   }
358   {
359     auto uncompressed = codec_->uncompress(compressed.get(),
360                                            uncompressedLength);
361     EXPECT_EQ(uncompressedLength, uncompressed->computeChainDataLength());
362     EXPECT_EQ(dh.hash(uncompressedLength), hashIOBuf(uncompressed.get()));
363   }
364
365   EXPECT_THROW(codec_->uncompress(compressed.get(), uncompressedLength + 1),
366                std::runtime_error);
367
368   // Corrupt the first character
369   ++(compressed->writableData()[0]);
370
371   if (!codec_->needsUncompressedLength()) {
372     EXPECT_THROW(codec_->uncompress(compressed.get()),
373                  std::runtime_error);
374   }
375
376   EXPECT_THROW(codec_->uncompress(compressed.get(), uncompressedLength),
377                std::runtime_error);
378 }
379
380 TEST_P(CompressionCorruptionTest, RandomData) {
381   runSimpleTest(randomDataHolder);
382 }
383
384 TEST_P(CompressionCorruptionTest, ConstantData) {
385   runSimpleTest(constantDataHolder);
386 }
387
388 INSTANTIATE_TEST_CASE_P(
389     CompressionCorruptionTest,
390     CompressionCorruptionTest,
391     testing::ValuesIn(
392         // NO_COMPRESSION can't detect corruption
393         // LZ4 can't detect corruption reliably (sigh)
394         supportedCodecs({
395             CodecType::SNAPPY,
396             CodecType::ZLIB,
397             CodecType::LZMA2,
398             CodecType::ZSTD,
399             CodecType::LZ4_FRAME,
400             CodecType::BZIP2,
401         })));
402
403 class AutomaticCodecTest : public testing::TestWithParam<CodecType> {
404  protected:
405   void SetUp() override {
406     codec_ = getCodec(GetParam());
407     auto_ = getAutoUncompressionCodec();
408   }
409
410   void runSimpleTest(const DataHolder& dh);
411
412   std::unique_ptr<Codec> codec_;
413   std::unique_ptr<Codec> auto_;
414 };
415
416 void AutomaticCodecTest::runSimpleTest(const DataHolder& dh) {
417   constexpr uint64_t uncompressedLength = 1000;
418   auto original = IOBuf::wrapBuffer(dh.data(uncompressedLength));
419   auto compressed = codec_->compress(original.get());
420
421   if (!codec_->needsUncompressedLength()) {
422     auto uncompressed = auto_->uncompress(compressed.get());
423     EXPECT_EQ(uncompressedLength, uncompressed->computeChainDataLength());
424     EXPECT_EQ(dh.hash(uncompressedLength), hashIOBuf(uncompressed.get()));
425   }
426   {
427     auto uncompressed = auto_->uncompress(compressed.get(), uncompressedLength);
428     EXPECT_EQ(uncompressedLength, uncompressed->computeChainDataLength());
429     EXPECT_EQ(dh.hash(uncompressedLength), hashIOBuf(uncompressed.get()));
430   }
431   ASSERT_GE(compressed->computeChainDataLength(), 8);
432   for (size_t i = 0; i < 8; ++i) {
433     auto split = compressed->clone();
434     auto rest = compressed->clone();
435     split->trimEnd(split->length() - i);
436     rest->trimStart(i);
437     split->appendChain(std::move(rest));
438     auto uncompressed = auto_->uncompress(split.get(), uncompressedLength);
439     EXPECT_EQ(uncompressedLength, uncompressed->computeChainDataLength());
440     EXPECT_EQ(dh.hash(uncompressedLength), hashIOBuf(uncompressed.get()));
441   }
442 }
443
444 TEST_P(AutomaticCodecTest, RandomData) {
445   runSimpleTest(randomDataHolder);
446 }
447
448 TEST_P(AutomaticCodecTest, ConstantData) {
449   runSimpleTest(constantDataHolder);
450 }
451
452 TEST_P(AutomaticCodecTest, ValidPrefixes) {
453   const auto prefixes = codec_->validPrefixes();
454   for (const auto& prefix : prefixes) {
455     EXPECT_FALSE(prefix.empty());
456     // Ensure that all strings are at least 8 bytes for LZMA2.
457     // The bytes after the prefix should be ignored by `canUncompress()`.
458     IOBuf data{IOBuf::COPY_BUFFER, prefix, 0, 8};
459     data.append(8);
460     EXPECT_TRUE(codec_->canUncompress(&data));
461     EXPECT_TRUE(auto_->canUncompress(&data));
462   }
463 }
464
465 TEST_P(AutomaticCodecTest, NeedsUncompressedLength) {
466   if (codec_->needsUncompressedLength()) {
467     EXPECT_TRUE(auto_->needsUncompressedLength());
468   }
469 }
470
471 TEST_P(AutomaticCodecTest, maxUncompressedLength) {
472   EXPECT_LE(codec_->maxUncompressedLength(), auto_->maxUncompressedLength());
473 }
474
475 TEST_P(AutomaticCodecTest, DefaultCodec) {
476   const uint64_t length = 42;
477   std::vector<std::unique_ptr<Codec>> codecs;
478   codecs.push_back(getCodec(CodecType::ZSTD));
479   auto automatic = getAutoUncompressionCodec(std::move(codecs));
480   auto original = IOBuf::wrapBuffer(constantDataHolder.data(length));
481   auto compressed = codec_->compress(original.get());
482   auto decompressed = automatic->uncompress(compressed.get());
483
484   EXPECT_EQ(constantDataHolder.hash(length), hashIOBuf(decompressed.get()));
485 }
486
487 namespace {
488 class CustomCodec : public Codec {
489  public:
490   static std::unique_ptr<Codec> create(std::string prefix, CodecType type) {
491     return make_unique<CustomCodec>(std::move(prefix), type);
492   }
493   explicit CustomCodec(std::string prefix, CodecType type)
494       : Codec(CodecType::USER_DEFINED),
495         prefix_(std::move(prefix)),
496         codec_(getCodec(type)) {}
497
498  private:
499   std::vector<std::string> validPrefixes() const override {
500     return {prefix_};
501   }
502
503   bool canUncompress(const IOBuf* data, Optional<uint64_t>) const override {
504     auto clone = data->cloneCoalescedAsValue();
505     if (clone.length() < prefix_.size()) {
506       return false;
507     }
508     return memcmp(clone.data(), prefix_.data(), prefix_.size()) == 0;
509   }
510
511   std::unique_ptr<IOBuf> doCompress(const IOBuf* data) override {
512     auto result = IOBuf::copyBuffer(prefix_);
513     result->appendChain(codec_->compress(data));
514     EXPECT_TRUE(canUncompress(result.get(), data->computeChainDataLength()));
515     return result;
516   }
517
518   std::unique_ptr<IOBuf> doUncompress(
519       const IOBuf* data,
520       Optional<uint64_t> uncompressedLength) override {
521     EXPECT_TRUE(canUncompress(data, uncompressedLength));
522     auto clone = data->cloneCoalescedAsValue();
523     clone.trimStart(prefix_.size());
524     return codec_->uncompress(&clone, uncompressedLength);
525   }
526
527   std::string prefix_;
528   std::unique_ptr<Codec> codec_;
529 };
530 }
531
532 TEST_P(AutomaticCodecTest, CustomCodec) {
533   const uint64_t length = 42;
534   auto ab = CustomCodec::create("ab", CodecType::ZSTD);
535   std::vector<std::unique_ptr<Codec>> codecs;
536   codecs.push_back(CustomCodec::create("ab", CodecType::ZSTD));
537   auto automatic = getAutoUncompressionCodec(std::move(codecs));
538   auto original = IOBuf::wrapBuffer(constantDataHolder.data(length));
539
540   auto abCompressed = ab->compress(original.get());
541   auto abDecompressed = automatic->uncompress(abCompressed.get());
542   EXPECT_TRUE(automatic->canUncompress(abCompressed.get()));
543   EXPECT_FALSE(auto_->canUncompress(abCompressed.get()));
544   EXPECT_EQ(constantDataHolder.hash(length), hashIOBuf(abDecompressed.get()));
545
546   auto compressed = codec_->compress(original.get());
547   auto decompressed = automatic->uncompress(compressed.get());
548   EXPECT_EQ(constantDataHolder.hash(length), hashIOBuf(decompressed.get()));
549 }
550
551 TEST_P(AutomaticCodecTest, CustomDefaultCodec) {
552   const uint64_t length = 42;
553   auto none = CustomCodec::create("none", CodecType::NO_COMPRESSION);
554   std::vector<std::unique_ptr<Codec>> codecs;
555   codecs.push_back(CustomCodec::create("none", CodecType::NO_COMPRESSION));
556   codecs.push_back(getCodec(CodecType::LZ4_FRAME));
557   auto automatic = getAutoUncompressionCodec(std::move(codecs));
558   auto original = IOBuf::wrapBuffer(constantDataHolder.data(length));
559
560   auto noneCompressed = none->compress(original.get());
561   auto noneDecompressed = automatic->uncompress(noneCompressed.get());
562   EXPECT_TRUE(automatic->canUncompress(noneCompressed.get()));
563   EXPECT_FALSE(auto_->canUncompress(noneCompressed.get()));
564   EXPECT_EQ(constantDataHolder.hash(length), hashIOBuf(noneDecompressed.get()));
565
566   auto compressed = codec_->compress(original.get());
567   auto decompressed = automatic->uncompress(compressed.get());
568   EXPECT_EQ(constantDataHolder.hash(length), hashIOBuf(decompressed.get()));
569 }
570
571 TEST_P(AutomaticCodecTest, canUncompressOneBytes) {
572   // No default codec can uncompress 1 bytes.
573   IOBuf buf{IOBuf::CREATE, 1};
574   buf.append(1);
575   EXPECT_FALSE(codec_->canUncompress(&buf, 1));
576   EXPECT_FALSE(codec_->canUncompress(&buf, folly::none));
577   EXPECT_FALSE(auto_->canUncompress(&buf, 1));
578   EXPECT_FALSE(auto_->canUncompress(&buf, folly::none));
579 }
580
581 INSTANTIATE_TEST_CASE_P(
582     AutomaticCodecTest,
583     AutomaticCodecTest,
584     testing::Values(
585         CodecType::LZ4_FRAME,
586         CodecType::ZSTD,
587         CodecType::ZLIB,
588         CodecType::GZIP,
589         CodecType::LZMA2,
590         CodecType::BZIP2));
591
592 TEST(ValidPrefixesTest, CustomCodec) {
593   std::vector<std::unique_ptr<Codec>> codecs;
594   codecs.push_back(CustomCodec::create("none", CodecType::NO_COMPRESSION));
595   const auto none = getAutoUncompressionCodec(std::move(codecs));
596   const auto prefixes = none->validPrefixes();
597   const auto it = std::find(prefixes.begin(), prefixes.end(), "none");
598   EXPECT_TRUE(it != prefixes.end());
599 }
600
601 #define EXPECT_THROW_IF_DEBUG(statement, expected_exception) \
602   do {                                                       \
603     if (kIsDebug) {                                          \
604       EXPECT_THROW((statement), expected_exception);         \
605     } else {                                                 \
606       EXPECT_NO_THROW((statement));                          \
607     }                                                        \
608   } while (false)
609
610 TEST(CheckCompatibleTest, SimplePrefixSecond) {
611   std::vector<std::unique_ptr<Codec>> codecs;
612   codecs.push_back(CustomCodec::create("abc", CodecType::NO_COMPRESSION));
613   codecs.push_back(CustomCodec::create("ab", CodecType::NO_COMPRESSION));
614   EXPECT_THROW_IF_DEBUG(
615       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
616 }
617
618 TEST(CheckCompatibleTest, SimplePrefixFirst) {
619   std::vector<std::unique_ptr<Codec>> codecs;
620   codecs.push_back(CustomCodec::create("ab", CodecType::NO_COMPRESSION));
621   codecs.push_back(CustomCodec::create("abc", CodecType::NO_COMPRESSION));
622   EXPECT_THROW_IF_DEBUG(
623       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
624 }
625
626 TEST(CheckCompatibleTest, Empty) {
627   std::vector<std::unique_ptr<Codec>> codecs;
628   codecs.push_back(CustomCodec::create("", CodecType::NO_COMPRESSION));
629   EXPECT_THROW_IF_DEBUG(
630       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
631 }
632
633 TEST(CheckCompatibleTest, ZstdPrefix) {
634   std::vector<std::unique_ptr<Codec>> codecs;
635   codecs.push_back(CustomCodec::create("\x28\xB5\x2F", CodecType::ZSTD));
636   EXPECT_THROW_IF_DEBUG(
637       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
638 }
639
640 TEST(CheckCompatibleTest, ZstdDuplicate) {
641   std::vector<std::unique_ptr<Codec>> codecs;
642   codecs.push_back(CustomCodec::create("\x28\xB5\x2F\xFD", CodecType::ZSTD));
643   EXPECT_THROW_IF_DEBUG(
644       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
645 }
646
647 TEST(CheckCompatibleTest, ZlibIsPrefix) {
648   std::vector<std::unique_ptr<Codec>> codecs;
649   codecs.push_back(CustomCodec::create("\x18\x76zzasdf", CodecType::ZSTD));
650   EXPECT_THROW_IF_DEBUG(
651       getAutoUncompressionCodec(std::move(codecs)), std::invalid_argument);
652 }
653 }}}  // namespaces
654
655 int main(int argc, char *argv[]) {
656   testing::InitGoogleTest(&argc, argv);
657   gflags::ParseCommandLineFlags(&argc, &argv, true);
658
659   auto ret = RUN_ALL_TESTS();
660   if (!ret) {
661     folly::runBenchmarksOnFlag();
662   }
663   return ret;
664 }