Chromium Code Reviews| Index: net/filter/gzip_source_stream_unittest.cc |
| diff --git a/net/filter/gzip_source_stream_unittest.cc b/net/filter/gzip_source_stream_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..021965a941bdc725145360d6684782417a61ed45 |
| --- /dev/null |
| +++ b/net/filter/gzip_source_stream_unittest.cc |
| @@ -0,0 +1,317 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "base/bind.h" |
| +#include "base/bit_cast.h" |
| +#include "base/callback.h" |
| +#include "net/base/completion_callback.h" |
| +#include "net/base/io_buffer.h" |
| +#include "net/base/test_completion_callback.h" |
| +#include "net/filter/gzip_source_stream.h" |
| +#include "net/filter/mock_source_stream.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "third_party/zlib/zlib.h" |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +const size_t kBufferSize = 4096; |
| + |
| +// How many bytes to leave unused at the end of |source_data_|. This margin is |
| +// present so that tests that need to append data after the zlib EOF do not run |
| +// out of room in the output buffer. |
| +const size_t kEOFMargin = 64; |
| + |
| +} // namespace |
| + |
| +class GzipSourceStreamTest : public ::testing::Test { |
| + protected: |
| + void Init() { Init(false); } |
|
Randy Smith (Not in Mondays)
2016/09/14 22:25:24
This is function overloading, which is discouraged
xunjieli
2016/09/19 13:57:03
Done.
|
| + |
| + // If |allow_gzip_fallback| is true, will use |
| + // GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK when constructing |gzip_stream_|. |
| + void Init(bool allow_gzip_fallback) { |
| + source_data_len_ = kBufferSize - kEOFMargin; |
| + |
| + for (size_t i = 0; i < source_data_len_; i++) |
| + source_data_[i] = i % 256; |
| + |
| + deflated_data_len_ = kBufferSize; |
| + |
| + Compress(source_data_, source_data_len_, deflated_data_, |
| + &deflated_data_len_, false); |
| + |
| + gzipped_data_len_ = kBufferSize; |
| + |
| + Compress(source_data_, source_data_len_, gzipped_data_, &gzipped_data_len_, |
| + true); |
| + |
| + output_buffer_ = new IOBuffer(kBufferSize); |
| + |
| + std::unique_ptr<MockSourceStream> deflate_source(new MockSourceStream); |
| + deflate_source_ = deflate_source.get(); |
| + deflate_stream_ = |
| + GzipSourceStream::Create(std::move(deflate_source), |
| + GzipSourceStream::GZIP_SOURCE_STREAM_DEFLATE); |
| + |
| + std::unique_ptr<MockSourceStream> source(new MockSourceStream); |
| + source_ = source.get(); |
| + if (allow_gzip_fallback) { |
| + gzip_stream_ = GzipSourceStream::Create( |
| + std::move(source), |
| + GzipSourceStream::GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK); |
| + } else { |
| + gzip_stream_ = GzipSourceStream::Create( |
| + std::move(source), GzipSourceStream::GZIP_SOURCE_STREAM_GZIP); |
| + } |
| + } |
| + |
| + void Compress(char* source, |
|
Randy Smith (Not in Mondays)
2016/09/14 22:25:24
Document? Mostly I want to make sure *dest_len is
xunjieli
2016/09/19 13:57:03
Done.
|
| + size_t source_len, |
| + char* dest, |
| + size_t* dest_len, |
| + bool gzip_framing) { |
| + size_t dest_left = *dest_len; |
| + z_stream zlib_stream; |
| + memset(&zlib_stream, 0, sizeof(zlib_stream)); |
| + int code; |
| + if (gzip_framing) { |
| + const int kMemLevel = 8; // the default, see deflateInit2(3) |
| + code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, |
| + -MAX_WBITS, kMemLevel, Z_DEFAULT_STRATEGY); |
| + } else { |
| + code = deflateInit(&zlib_stream, Z_DEFAULT_COMPRESSION); |
| + } |
| + DCHECK_EQ(Z_OK, code); |
| + |
| + // If compressing with gzip framing, prepend a gzip header. See RFC 1952 2.2 |
| + // and 2.3 for more information. |
| + if (gzip_framing) { |
| + const unsigned char gzip_header[] = { |
| + 0x1f, |
| + 0x8b, // magic number |
| + 0x08, // CM 0x08 == "deflate" |
| + 0x00, // FLG 0x00 == nothing |
| + 0x00, 0x00, 0x00, |
| + 0x00, // MTIME 0x00000000 == no mtime |
| + 0x00, // XFL 0x00 == nothing |
| + 0xff, // OS 0xff == unknown |
| + }; |
| + DCHECK_GE(dest_left, sizeof(gzip_header)); |
| + memcpy(dest, gzip_header, sizeof(gzip_header)); |
| + dest += sizeof(gzip_header); |
| + dest_left -= sizeof(gzip_header); |
| + } |
| + |
| + zlib_stream.next_in = bit_cast<Bytef*>(source); |
| + zlib_stream.avail_in = source_len; |
| + zlib_stream.next_out = bit_cast<Bytef*>(dest); |
| + zlib_stream.avail_out = dest_left; |
| + |
| + code = deflate(&zlib_stream, Z_FINISH); |
| + DCHECK_EQ(Z_STREAM_END, code); |
| + dest_left = zlib_stream.avail_out; |
| + |
| + deflateEnd(&zlib_stream); |
| + *dest_len -= dest_left; |
| + } |
| + |
| + char* source_data() { return source_data_; } |
| + size_t source_data_len() { return source_data_len_; } |
| + |
| + char* deflated_data() { return deflated_data_; } |
| + size_t deflated_data_len() { return deflated_data_len_; } |
| + |
| + char* gzipped_data() { return gzipped_data_; } |
| + size_t gzipped_data_len() { return gzipped_data_len_; } |
| + |
| + IOBuffer* output_buffer() { return output_buffer_.get(); } |
| + char* output_data() { return output_buffer_->data(); } |
| + size_t output_buffer_size() { return kBufferSize; } |
| + |
| + MockSourceStream* deflate_source() { return deflate_source_; } |
| + MockSourceStream* source() { return source_; } |
| + GzipSourceStream* gzip_stream() { return gzip_stream_.get(); } |
| + |
| + void AddTrailingDeflatedData(const char* data, size_t data_len) { |
| + DCHECK_LE(data_len, kBufferSize - deflated_data_len_); |
| + memcpy(deflated_data_ + deflated_data_len_, data, data_len); |
| + deflated_data_len_ += data_len; |
| + } |
| + |
| + int ReadStream(GzipSourceStream* stream, const CompletionCallback& callback) { |
| + return stream->Read(output_buffer(), output_buffer_size(), callback); |
| + } |
| + |
| + int ReadDeflateStream(const CompletionCallback& callback) { |
| + return ReadStream(deflate_stream_.get(), callback); |
| + } |
| + |
| + int ReadGzipStream(const CompletionCallback& callback) { |
| + return ReadStream(gzip_stream_.get(), callback); |
| + } |
| + |
| + private: |
| + char source_data_[kBufferSize]; |
| + size_t source_data_len_; |
| + |
| + char deflated_data_[kBufferSize]; |
| + size_t deflated_data_len_; |
| + |
| + char gzipped_data_[kBufferSize]; |
| + size_t gzipped_data_len_; |
| + |
| + scoped_refptr<IOBuffer> output_buffer_; |
| + |
| + MockSourceStream* deflate_source_; |
| + std::unique_ptr<GzipSourceStream> deflate_stream_; |
| + MockSourceStream* source_; |
| + std::unique_ptr<GzipSourceStream> gzip_stream_; |
| +}; |
| + |
| +TEST_F(GzipSourceStreamTest, EmptyStream) { |
| + Init(); |
| + deflate_source()->AddReadResult("", 0, OK, MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int result = ReadDeflateStream(callback.callback()); |
| + EXPECT_EQ(OK, result); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
|
Randy Smith (Not in Mondays)
2016/09/14 22:25:24
Why test gzip_stream() description in a test that'
xunjieli
2016/09/19 13:57:03
Done. Good catch!
|
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, DeflateOneBlockSync) { |
| + Init(); |
| + deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int result = ReadDeflateStream(callback.callback()); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), result); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, GzipOneBlockSync) { |
| + Init(); |
| + source()->AddReadResult(gzipped_data(), gzipped_data_len(), OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int result = ReadGzipStream(callback.callback()); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), result); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, DeflateTwoBlocksSync) { |
| + Init(); |
| + deflate_source()->AddReadResult(deflated_data(), 10, OK, |
| + MockSourceStream::SYNC); |
| + deflate_source()->AddReadResult(deflated_data() + 10, |
| + deflated_data_len() - 10, OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + char buffer[kBufferSize]; |
| + int first_bytes_read = ReadDeflateStream(callback.callback()); |
| + EXPECT_LE(OK, first_bytes_read); |
| + memcpy(buffer, output_data(), first_bytes_read); |
| + int second_bytes_read = ReadDeflateStream(callback.callback()); |
| + EXPECT_LE(OK, second_bytes_read); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), |
| + first_bytes_read + second_bytes_read); |
| + memcpy(buffer + first_bytes_read, output_data(), second_bytes_read); |
| + memcpy(output_data(), buffer, first_bytes_read + second_bytes_read); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, DeflateOneBlockAsync) { |
| + Init(); |
| + deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, |
| + MockSourceStream::ASYNC); |
| + TestCompletionCallback callback; |
| + EXPECT_EQ(ERR_IO_PENDING, ReadDeflateStream(callback.callback())); |
| + deflate_source()->CompleteNextRead(); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), callback.WaitForResult()); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, PassThroughAfterEOF) { |
| + Init(); |
| + char test_data[] = "Hello, World!"; |
| + AddTrailingDeflatedData(test_data, sizeof(test_data)); |
| + deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, |
| + MockSourceStream::SYNC); |
| + // Compressed and uncompressed data get returned as separate Read() results, |
| + // so this test has to call Read twice. |
| + TestCompletionCallback callback; |
| + int bytes_read = ReadDeflateStream(callback.callback()); |
| + EXPECT_EQ(static_cast<int>(source_data_len() + sizeof(test_data)), |
| + bytes_read); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ(0, memcmp(output_data() + source_data_len(), test_data, |
| + sizeof(test_data))); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, MissingZlibHeader) { |
| + Init(); |
| + const size_t kZlibHeaderLen = 2; |
| + deflate_source()->AddReadResult(deflated_data() + kZlibHeaderLen, |
| + deflated_data_len() - kZlibHeaderLen, OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int bytes_read = ReadDeflateStream(callback.callback()); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), bytes_read); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +// A slight wrinkle: since the test GZip stream runs with gzip_fallback set to |
| +// true, corrupting the first byte instead of the second byte will cause the |
| +// gzip stream to falsely accept the corrupt data and pass it through untouched. |
|
Randy Smith (Not in Mondays)
2016/09/14 22:25:24
nitty nit: I'd put this comment just before the as
xunjieli
2016/09/19 13:57:03
Done. I think this is an old comment that no longe
|
| +TEST_F(GzipSourceStreamTest, CorruptGzipHeader) { |
| + Init(); |
| + gzipped_data()[1] = 0; |
| + source()->AddReadResult(gzipped_data(), gzipped_data_len(), OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int bytes_read = ReadGzipStream(callback.callback()); |
| + EXPECT_EQ(ERR_CONTENT_DECODING_FAILED, bytes_read); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +TEST_F(GzipSourceStreamTest, GzipFallback) { |
| + Init(/*allow_gzip_fallback=*/true); |
| + source()->AddReadResult(source_data(), source_data_len(), OK, |
| + MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int bytes_read = ReadGzipStream(callback.callback()); |
| + EXPECT_EQ(static_cast<int>(source_data_len()), bytes_read); |
| + EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
| +// This test checks that the gzip stream source works correctly on 'golden' data |
| +// as produced by gzip(1). |
| +TEST_F(GzipSourceStreamTest, GzipCorrectness) { |
| + Init(); |
| + char plain_data[] = "Hello, World!"; |
| + unsigned char gzip_data[] = { |
| + // From: |
| + // echo -n 'Hello, World!' | gzip | xxd -i | sed -e 's/^/ /' |
| + 0x1f, 0x8b, 0x08, 0x00, 0x2b, 0x02, 0x84, 0x55, 0x00, 0x03, 0xf3, |
| + 0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x51, 0x08, 0xcf, 0x2f, 0xca, 0x49, |
| + 0x51, 0x04, 0x00, 0xd0, 0xc3, 0x4a, 0xec, 0x0d, 0x00, 0x00, 0x00}; |
| + source()->AddReadResult(reinterpret_cast<char*>(gzip_data), sizeof(gzip_data), |
| + OK, MockSourceStream::SYNC); |
| + TestCompletionCallback callback; |
| + int bytes_read = ReadGzipStream(callback.callback()); |
| + // Using strlen(3) here because echo doesn't leave the null terminator on the |
| + // string. |
| + EXPECT_EQ(static_cast<int>(strlen(plain_data)), bytes_read); |
| + EXPECT_EQ(0, memcmp(output_data(), plain_data, strlen(plain_data))); |
| + EXPECT_EQ("GZIP", gzip_stream()->Description()); |
| +} |
| + |
|
Randy Smith (Not in Mondays)
2016/09/14 22:25:24
Test for both missing gzip footer and present gzip
xunjieli
2016/09/19 13:57:03
Done. Great idea!
|
| +} // namespace |