Index: net/filter/sdch_source_stream_unittest.cc |
diff --git a/net/filter/sdch_source_stream_unittest.cc b/net/filter/sdch_source_stream_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c441d73201facd30020b6d4614e7887d8483e0b8 |
--- /dev/null |
+++ b/net/filter/sdch_source_stream_unittest.cc |
@@ -0,0 +1,546 @@ |
+// 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 <string> |
+ |
+#include "base/bind.h" |
+#include "base/bit_cast.h" |
+#include "base/callback.h" |
+#include "base/memory/ptr_util.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 "net/filter/sdch_source_stream.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "third_party/zlib/zlib.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+const size_t kBufferSize = 4096; |
+const size_t kSmallBufferSize = 1; |
+ |
+// Provide sample data and compression results with a sample VCDIFF dictionary. |
+// Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary. |
+static const char kTestVcdiffDictionary[] = |
+ "DictionaryFor" |
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n"; |
+ |
+// Pre-compression test data. Note that we pad with a lot of highly gzip |
+// compressible content to help to exercise the chaining pipeline. That is why |
+// there are a PILE of zeros at the start and end. |
+static const char kTestData[] = |
+ "0000000000000000000000000000000000000000000000" |
+ "0000000000000000000000000000TestData " |
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression" |
+ "00000000000000000000000000000000000000000000000000000000000000000000000000" |
+ "000000000000000000000000000000000000000\n"; |
+static const char kSdchCompressedTestData[] = |
+ "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001" |
+ "00000000000000000000000000000000000000000000000000000000000000000000000000" |
+ "TestData 00000000000000000000000000000000000000000000000000000000000000000" |
+ "000000000000000000000000000000000000000000000000\n\001S\023\077\001r\r"; |
+ |
+// Helper function to perform gzip compression of data. |
+static std::string gzip_compress(const std::string& input, |
+ size_t input_size, |
+ size_t* output_size) { |
+ z_stream zlib_stream; |
+ memset(&zlib_stream, 0, sizeof(zlib_stream)); |
+ int code; |
+ |
+ // Initialize zlib |
+ code = |
+ deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, |
+ 8, // DEF_MEM_LEVEL |
+ Z_DEFAULT_STRATEGY); |
+ |
+ CHECK_EQ(Z_OK, code); |
+ |
+ // Fill in zlib control block |
+ zlib_stream.next_in = bit_cast<Bytef*>(input.data()); |
+ zlib_stream.avail_in = input_size; |
+ |
+ // Assume we can compress into similar buffer (add 100 bytes to be sure). |
+ size_t gzip_compressed_length = zlib_stream.avail_in + 100; |
+ std::unique_ptr<char[]> gzip_compressed(new char[gzip_compressed_length]); |
+ zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get()); |
+ zlib_stream.avail_out = gzip_compressed_length; |
+ |
+ // The GZIP header (see RFC 1952): |
+ // +---+---+---+---+---+---+---+---+---+---+ |
+ // |ID1|ID2|CM |FLG| MTIME |XFL|OS | |
+ // +---+---+---+---+---+---+---+---+---+---+ |
+ // ID1 \037 |
+ // ID2 \213 |
+ // CM \010 (compression method == DEFLATE) |
+ // FLG \000 (special flags that we do not support) |
+ // MTIME Unix format modification time (0 means not available) |
+ // XFL 2-4? DEFLATE flags |
+ // OS ???? Operating system indicator (255 means unknown) |
+ // |
+ // Header value we generate: |
+ const char kGZipHeader[] = {'\037', '\213', '\010', '\000', '\000', |
+ '\000', '\000', '\000', '\002', '\377'}; |
+ CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader)); |
+ memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader)); |
+ zlib_stream.next_out += sizeof(kGZipHeader); |
+ zlib_stream.avail_out -= sizeof(kGZipHeader); |
+ |
+ // Do deflate |
+ code = deflate(&zlib_stream, Z_FINISH); |
+ gzip_compressed_length -= zlib_stream.avail_out; |
+ std::string compressed(gzip_compressed.get(), gzip_compressed_length); |
+ deflateEnd(&zlib_stream); |
+ *output_size = gzip_compressed_length; |
+ return compressed; |
+} |
+ |
+class MockDelegate : public SdchSourceStream::Delegate { |
+ public: |
+ MockDelegate(const std::string& test_dictionary_id, |
+ const std::string& test_dictionary_text, |
+ SdchSourceStream::Delegate::ErrorRecovery error_recover, |
+ const std::string& replace_output) |
+ : dictionary_id_error_handled_(false), |
+ get_dictionary_error_handled_(false), |
+ decoding_error_handled_(false), |
+ test_dictionary_id_(test_dictionary_id), |
+ test_dictionary_text_(test_dictionary_text), |
+ error_recover_(error_recover), |
+ replace_output_(replace_output) {} |
+ |
+ // SdchSourceStream::Delegate implementation. |
+ SdchSourceStream::Delegate::ErrorRecovery OnDictionaryIdError( |
+ std::string* replace_output) override { |
+ dictionary_id_error_handled_ = true; |
+ if (error_recover_ == REPLACE_OUTPUT) |
+ *replace_output = replace_output_; |
+ return error_recover_; |
+ } |
+ |
+ SdchSourceStream::Delegate::ErrorRecovery OnGetDictionaryError( |
+ std::string* replace_output) override { |
+ get_dictionary_error_handled_ = true; |
+ if (error_recover_ == REPLACE_OUTPUT) |
+ *replace_output = replace_output_; |
+ return error_recover_; |
+ } |
+ |
+ SdchSourceStream::Delegate::ErrorRecovery OnDecodingError( |
+ std::string* replace_output) override { |
+ decoding_error_handled_ = true; |
+ if (error_recover_ == REPLACE_OUTPUT) |
+ *replace_output = replace_output_; |
+ return error_recover_; |
+ } |
+ |
+ bool OnGetDictionary(const std::string& server_id, |
+ const std::string** text) override { |
+ last_get_dictionary_id_ = server_id; |
+ if (server_id == test_dictionary_id_) { |
+ *text = &test_dictionary_text_; |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ void OnStreamDestroyed(SdchSourceStream::InputState input_state, |
+ bool buffered_output_present, |
+ bool decoding_not_finished) override {} |
+ |
+ bool dictionary_id_error_handled() { return dictionary_id_error_handled_; } |
+ bool get_dictionary_error_handled() { return get_dictionary_error_handled_; } |
+ bool decoding_error_handled() { return decoding_error_handled_; } |
+ std::string last_get_dictionary_id() { return last_get_dictionary_id_; } |
+ |
+ private: |
+ std::string last_get_dictionary_id_; |
+ bool dictionary_id_error_handled_; |
+ bool get_dictionary_error_handled_; |
+ bool decoding_error_handled_; |
+ std::string test_dictionary_id_; |
+ std::string test_dictionary_text_; |
+ SdchSourceStream::Delegate::ErrorRecovery error_recover_; |
+ std::string replace_output_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MockDelegate); |
+}; |
+} // namespace |
+ |
+class SdchSourceStreamTest |
+ : public ::testing::TestWithParam<MockSourceStream::Mode> { |
+ public: |
+ SdchSourceStreamTest() : out_buffer_size_(kBufferSize) {} |
+ |
+ void Init() { |
+ out_buffer_ = new IOBufferWithSize(out_buffer_size_); |
+ std::unique_ptr<MockSourceStream> source(new MockSourceStream); |
+ source_ = source.get(); |
+ std::unique_ptr<MockDelegate> delegate(GetNewDelegate()); |
+ delegate_ = delegate.get(); |
+ sdch_source_.reset(new SdchSourceStream( |
+ std::move(source), std::move(delegate), SourceStream::TYPE_SDCH)); |
+ } |
+ |
+ // If MockSourceStream::Mode is ASYNC, completes 1 read from |
+ // |mock_stream| and wait for |callback| to complete. If Mode is not ASYNC, |
+ // does nothing and returns |previous_result|. |
+ int CompleteReadIfAsync(int previous_result, |
+ TestCompletionCallback* callback, |
+ MockSourceStream* mock_stream) { |
+ if (GetParam() == MockSourceStream::ASYNC) { |
+ EXPECT_EQ(ERR_IO_PENDING, previous_result); |
+ mock_stream->CompleteNextRead(); |
+ return callback->WaitForResult(); |
+ } |
+ return previous_result; |
+ } |
+ |
+ int CompleteReadIfAsync(int previous_result, |
+ TestCompletionCallback* callback, |
+ MockSourceStream* mock_stream, |
+ int num_reads) { |
+ if (GetParam() == MockSourceStream::ASYNC) { |
+ EXPECT_EQ(ERR_IO_PENDING, previous_result); |
+ while (num_reads > 0) { |
+ mock_stream->CompleteNextRead(); |
+ num_reads--; |
+ } |
+ return callback->WaitForResult(); |
+ } |
+ return previous_result; |
+ } |
+ |
+ IOBuffer* out_buffer() { return out_buffer_.get(); } |
+ char* out_data() { return out_buffer_->data(); } |
+ size_t out_buffer_size() { return out_buffer_size_; } |
+ |
+ MockSourceStream* mock_source() { return source_; } |
+ SdchSourceStream* sdch_source() { return sdch_source_.get(); } |
+ |
+ int ReadStream(const TestCompletionCallback& callback) { |
+ return sdch_source()->Read(out_buffer(), out_buffer_size(), |
+ callback.callback()); |
+ } |
+ |
+ void set_out_buffer_size(int out_buffer_size) { |
+ out_buffer_size_ = out_buffer_size; |
+ } |
+ |
+ void SetTestDictionary(const std::string& dictionary_id, |
+ const std::string& dictionary_text) { |
+ test_dictionary_id_ = dictionary_id; |
+ test_dictionary_text_ = dictionary_text; |
+ } |
+ |
+ void SetErrorRecovery(SdchSourceStream::Delegate::ErrorRecovery error_recover, |
+ const std::string& replace_output) { |
+ error_recover_ = error_recover; |
+ replace_output_ = replace_output; |
+ } |
+ |
+ void AppendDictionaryIdTo(std::string* resp, std::string* server_id) { |
+ std::string client_id; |
+ SdchManager::GenerateHash(kTestVcdiffDictionary, &client_id, server_id); |
+ SetTestDictionary(*server_id, kTestVcdiffDictionary); |
+ std::string response(server_id->data(), server_id->size()); |
+ response.append("\0"); |
+ resp->append(response.data(), server_id->size() + 1); |
+ } |
+ |
+ MockDelegate* delegate() { return delegate_; } |
+ |
+ // Gets a new MockDelegate and take ownership of it. |
+ std::unique_ptr<MockDelegate> GetNewDelegate() { |
+ return base::WrapUnique(new MockDelegate(test_dictionary_id_, |
+ test_dictionary_text_, |
+ error_recover_, replace_output_)); |
+ } |
+ |
+ private: |
+ // Owned by |sdch_source_|. |
+ MockSourceStream* source_; |
+ MockDelegate* delegate_; |
+ |
+ std::unique_ptr<SdchSourceStream> sdch_source_; |
+ scoped_refptr<IOBufferWithSize> out_buffer_; |
+ int out_buffer_size_; |
+ std::string test_dictionary_id_; |
+ std::string test_dictionary_text_; |
+ SdchSourceStream::Delegate::ErrorRecovery error_recover_; |
+ std::string replace_output_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SdchSourceStreamTest); |
+}; |
+ |
+TEST(SdchSourceStreamTest, GetTypeAsString) { |
+ SourceStream::SourceType types[] = {SourceStream::TYPE_SDCH_POSSIBLE, |
+ SourceStream::TYPE_SDCH}; |
+ for (auto type : types) { |
+ std::unique_ptr<MockSourceStream> mock_source(new MockSourceStream()); |
+ std::unique_ptr<MockDelegate> dummy_delegate( |
+ new MockDelegate("", "", SdchSourceStream::Delegate::NONE, "")); |
+ |
+ SdchSourceStream stream(std::move(mock_source), std::move(dummy_delegate), |
+ type); |
+ EXPECT_EQ( |
+ type == SourceStream::TYPE_SDCH_POSSIBLE ? "SDCH_POSSIBLE" : "SDCH", |
+ stream.Description()); |
+ } |
+} |
+ |
+INSTANTIATE_TEST_CASE_P(SdchSourceStreamTests, |
+ SdchSourceStreamTest, |
+ ::testing::Values(MockSourceStream::SYNC, |
+ MockSourceStream::ASYNC)); |
+ |
+TEST_P(SdchSourceStreamTest, EmptyStream) { |
+ Init(); |
+ mock_source()->AddReadResult("", 0, OK, GetParam()); |
+ TestCompletionCallback callback; |
+ int result = ReadStream(callback); |
+ result = CompleteReadIfAsync(result, &callback, mock_source()); |
+ EXPECT_EQ(OK, result); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+// Ensure that GetDictionary() is not called at all if the SDCH dictionary ID is |
+// malformed. |
+TEST_P(SdchSourceStreamTest, BogusDictionaryId) { |
+ char id[] = {0x1f, '0', '0', '0', '0', '0', '0', '0', 0x0}; |
+ SetTestDictionary(id, "..."); |
+ SetErrorRecovery(SdchSourceStream::Delegate::PASS_THROUGH, std::string()); |
+ Init(); |
+ mock_source()->AddReadResult(id, sizeof(id), OK, GetParam()); |
+ TestCompletionCallback callback; |
+ int result = ReadStream(callback); |
+ result = CompleteReadIfAsync(result, &callback, mock_source()); |
+ |
+ EXPECT_TRUE(delegate()->dictionary_id_error_handled()); |
+ EXPECT_EQ("", delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(9, result); |
+ EXPECT_EQ(0, memcmp(id, out_data(), result)); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+// When encounter a dictionary error, delegate returns ErrorRecovery NONE. |
+TEST_P(SdchSourceStreamTest, BogusDictionaryIdNoRecover) { |
+ char id[] = {0x1f, '0', '0', '0', '0', '0', '0', '0', 0x0}; |
+ SetTestDictionary(id, "..."); |
+ SetErrorRecovery(SdchSourceStream::Delegate::NONE, std::string()); |
+ Init(); |
+ mock_source()->AddReadResult(id, sizeof(id), OK, GetParam()); |
+ TestCompletionCallback callback; |
+ int result = ReadStream(callback); |
+ result = CompleteReadIfAsync(result, &callback, mock_source()); |
+ |
+ EXPECT_TRUE(delegate()->dictionary_id_error_handled()); |
+ EXPECT_EQ("", delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(ERR_CONTENT_DECODING_FAILED, result); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+// Ensure that the stream's dictionary error handler is called if GetDictionary |
+// returns no dictionary. |
+TEST_P(SdchSourceStreamTest, NoDictionaryError) { |
+ char id[] = "00000000"; |
+ SetErrorRecovery(SdchSourceStream::Delegate::PASS_THROUGH, std::string()); |
+ Init(); |
+ mock_source()->AddReadResult(id, sizeof(id), OK, GetParam()); |
+ TestCompletionCallback callback; |
+ int result = ReadStream(callback); |
+ result = CompleteReadIfAsync(result, &callback, mock_source()); |
+ EXPECT_EQ(9, result); |
+ EXPECT_TRUE(delegate()->get_dictionary_error_handled()); |
+ EXPECT_EQ(id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(0, memcmp(id, out_data(), result)); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+TEST_P(SdchSourceStreamTest, DictionaryLoaded) { |
+ std::string response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&response, &server_id); |
+ Init(); |
+ mock_source()->AddReadResult(response.data(), response.size(), OK, |
+ GetParam()); |
+ mock_source()->AddReadResult(response.data(), 0, OK, MockSourceStream::SYNC); |
+ TestCompletionCallback callback; |
+ int rv = ReadStream(callback); |
+ rv = CompleteReadIfAsync(rv, &callback, mock_source()); |
+ // Decoded response should be empty. |
+ EXPECT_EQ(0, rv); |
+ EXPECT_FALSE(delegate()->get_dictionary_error_handled()); |
+ EXPECT_EQ(server_id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+TEST_P(SdchSourceStreamTest, DecompressOneBlock) { |
+ std::string response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&response, &server_id); |
+ Init(); |
+ response.append(kSdchCompressedTestData, sizeof(kSdchCompressedTestData) - 1); |
+ mock_source()->AddReadResult(response.data(), response.size(), OK, |
+ GetParam()); |
+ TestCompletionCallback callback; |
+ int rv = ReadStream(callback); |
+ rv = CompleteReadIfAsync(rv, &callback, mock_source()); |
+ |
+ EXPECT_FALSE(delegate()->decoding_error_handled()); |
+ EXPECT_EQ(server_id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), rv); |
+ EXPECT_EQ(0, memcmp(kTestData, out_data(), rv)); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+TEST_P(SdchSourceStreamTest, DecompressWithSmallOutputBuffer) { |
+ set_out_buffer_size(kSmallBufferSize); |
+ std::string response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&response, &server_id); |
+ Init(); |
+ response.append(kSdchCompressedTestData, sizeof(kSdchCompressedTestData) - 1); |
+ mock_source()->AddReadResult(response.data(), response.size(), OK, |
+ GetParam()); |
+ // Add a 0 byte read to signal EOF. |
+ mock_source()->AddReadResult(kSdchCompressedTestData, 0, OK, |
+ MockSourceStream::SYNC); |
+ |
+ std::string actual_output; |
+ while (true) { |
+ TestCompletionCallback callback; |
+ int rv = ReadStream(callback); |
+ if (rv == ERR_IO_PENDING) |
+ rv = CompleteReadIfAsync(rv, &callback, mock_source()); |
+ if (rv == OK) |
+ break; |
+ ASSERT_GT(rv, OK); |
+ EXPECT_GE(kSmallBufferSize, static_cast<size_t>(rv)); |
+ actual_output.append(out_data(), rv); |
+ } |
+ EXPECT_FALSE(delegate()->decoding_error_handled()); |
+ EXPECT_EQ(server_id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(sizeof(kTestData) - 1, actual_output.size()); |
+ EXPECT_EQ(kTestData, actual_output); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+TEST_P(SdchSourceStreamTest, DecompressWithSmallInputBuffer) { |
+ std::string response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&response, &server_id); |
+ Init(); |
+ response.append(kSdchCompressedTestData, sizeof(kSdchCompressedTestData) - 1); |
+ // Add a sequence of small reads. |
+ for (size_t i = 0; i < response.size(); i++) { |
+ mock_source()->AddReadResult(response.data() + i, 1, OK, |
+ MockSourceStream::SYNC); |
+ } |
+ // Add a 0 byte read to signal EOF. |
+ mock_source()->AddReadResult(kSdchCompressedTestData, 0, OK, |
+ MockSourceStream::SYNC); |
+ std::string actual_output; |
+ while (true) { |
+ TestCompletionCallback callback; |
+ int rv = ReadStream(callback); |
+ if (rv == ERR_IO_PENDING) |
+ rv = CompleteReadIfAsync(rv, &callback, mock_source()); |
+ if (rv == OK) |
+ break; |
+ actual_output.append(out_data(), rv); |
+ } |
+ EXPECT_FALSE(delegate()->decoding_error_handled()); |
+ EXPECT_EQ(server_id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(sizeof(kTestData) - 1, actual_output.size()); |
+ EXPECT_EQ(kTestData, actual_output); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+TEST_P(SdchSourceStreamTest, DecompressTwoBlocks) { |
+ std::string response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&response, &server_id); |
+ Init(); |
+ response.append(kSdchCompressedTestData, sizeof(kSdchCompressedTestData) - 1); |
+ mock_source()->AddReadResult(response.data(), 32, OK, GetParam()); |
+ mock_source()->AddReadResult(response.data() + 32, response.size() - 32, OK, |
+ GetParam()); |
+ mock_source()->AddReadResult(kSdchCompressedTestData, 0, OK, |
+ MockSourceStream::SYNC); |
+ std::string actual_output; |
+ while (true) { |
+ TestCompletionCallback callback; |
+ int rv = ReadStream(callback); |
+ if (rv == ERR_IO_PENDING) |
+ rv = CompleteReadIfAsync(rv, &callback, mock_source(), 2); |
+ if (rv == OK) |
+ break; |
+ ASSERT_GT(rv, OK); |
+ actual_output.append(out_data(), rv); |
+ } |
+ EXPECT_FALSE(delegate()->decoding_error_handled()); |
+ EXPECT_EQ(server_id, delegate()->last_get_dictionary_id()); |
+ EXPECT_EQ(sizeof(kTestData) - 1, actual_output.size()); |
+ EXPECT_EQ(kTestData, actual_output); |
+ EXPECT_EQ("SDCH", sdch_source()->Description()); |
+} |
+ |
+// Test that filters can be cascaded (chained) so that the output of one filter |
+// is processed by the next one. This is most critical for SDCH, which is |
+// routinely followed by gzip (during encoding). The filter we'll test for will |
+// do the gzip decoding first, and then decode the SDCH content. |
+TEST_P(SdchSourceStreamTest, FilterChaining) { |
+ int out_buffer_sizes[] = {kBufferSize, kSmallBufferSize}; |
+ for (auto out_buffer_size : out_buffer_sizes) { |
+ set_out_buffer_size(out_buffer_size); |
+ std::string sdch_response; |
+ std::string server_id; |
+ AppendDictionaryIdTo(&sdch_response, &server_id); |
+ Init(); |
+ sdch_response.append(kSdchCompressedTestData, |
+ sizeof(kSdchCompressedTestData) - 1); |
+ size_t expected_length = |
+ server_id.length() + sizeof(kSdchCompressedTestData); |
+ size_t gzip_length; |
+ std::string gzip_compressed_sdch = |
+ gzip_compress(sdch_response, expected_length, &gzip_length); |
+ MockSourceStream* source = new MockSourceStream; |
+ source->AddReadResult(gzip_compressed_sdch.data(), gzip_length, OK, |
+ GetParam()); |
+ // Add a 0 byte read to signal EOF. |
+ source->AddReadResult(gzip_compressed_sdch.data(), 0, OK, |
+ MockSourceStream::SYNC); |
+ std::unique_ptr<GzipSourceStream> gzip_source = GzipSourceStream::Create( |
+ base::WrapUnique(source), SourceStream::TYPE_GZIP); |
+ std::unique_ptr<MockDelegate> delegate = GetNewDelegate(); |
+ MockDelegate* raw_delegate_pointer = delegate.get(); |
+ std::unique_ptr<SdchSourceStream> sdch_source(new SdchSourceStream( |
+ std::move(gzip_source), std::move(delegate), SourceStream::TYPE_SDCH)); |
+ std::string actual_output; |
+ while (true) { |
+ TestCompletionCallback callback; |
+ int rv = |
+ sdch_source->Read(out_buffer(), out_buffer_size, callback.callback()); |
+ if (rv == ERR_IO_PENDING) |
+ rv = CompleteReadIfAsync(rv, &callback, source); |
+ if (rv == OK) |
+ break; |
+ ASSERT_GT(rv, OK); |
+ if (out_buffer_size == kSmallBufferSize) |
+ EXPECT_GE(kSmallBufferSize, static_cast<size_t>(rv)); |
+ actual_output.append(out_data(), rv); |
+ } |
+ EXPECT_FALSE(raw_delegate_pointer->decoding_error_handled()); |
+ EXPECT_EQ(server_id, raw_delegate_pointer->last_get_dictionary_id()); |
+ EXPECT_EQ(sizeof(kTestData) - 1, actual_output.size()); |
+ EXPECT_EQ(kTestData, actual_output); |
+ EXPECT_EQ("GZIP,SDCH", sdch_source->Description()); |
+ } |
+} |
+ |
+} // namespace net |