Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2026)

Unified Diff: net/filter/sdch_source_stream_unittest.cc

Issue 2368433002: Add net::SdchSourceStream and net::SdchPolicyDelegate (Closed)
Patch Set: fix meta refresh Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..c5b78f55493588e5460a1b7a87d941b226a98567
--- /dev/null
+++ b/net/filter/sdch_source_stream_unittest.cc
@@ -0,0 +1,517 @@
+// 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 char* kDecodingError = "decoding error";
+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;
+}
+
+} // namespace
+
+class SdchSourceStreamTest
+ : public ::testing::TestWithParam<MockSourceStream::Mode>,
+ public SdchSourceStream::Delegate {
+ public:
+ SdchSourceStreamTest()
+ : dictionary_id_error_handled_(false),
+ get_dictionary_error_handled_(false),
+ decoding_error_handled_(false),
+ error_recover_(NONE) {}
+
+ void SetUp() override {
+ ::testing::Test::SetUp();
+
+ out_buffer_ = new IOBufferWithSize(kBufferSize);
+
+ std::unique_ptr<MockSourceStream> source(new MockSourceStream);
+ source_ = source.get();
+ sdch_source_.reset(new SdchSourceStream(std::move(source), this));
+ }
+
+ // 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(); }
+
+ 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_; }
+
+ int ReadStream(const TestCompletionCallback& callback) {
+ return sdch_source()->Read(out_buffer(), out_buffer_size(),
+ callback.callback());
+ }
+
+ void SetTestDictionary(const std::string& dictionary_id,
+ const std::string& dictionary_text) {
+ test_dictionary_id_ = dictionary_id;
+ test_dictionary_text_ = dictionary_text;
+ }
+
+ void SetErrorRecover(SdchSourceStream::Delegate::ErrorRecover 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);
+ }
+
+ // SdchSourceStream::Delegate implementation.
+ SdchSourceStream::Delegate::ErrorRecover OnDictionaryIdError(
+ SdchSourceStream* source,
+ std::string* replace_output) override {
+ dictionary_id_error_handled_ = true;
+ if (error_recover_ == REPLACE_OUTPUT)
+ *replace_output = replace_output_;
+ return error_recover_;
+ }
+
+ SdchSourceStream::Delegate::ErrorRecover OnGetDictionaryError(
+ SdchSourceStream* source,
+ std::string* replace_output) override {
+ get_dictionary_error_handled_ = true;
+ if (error_recover_ == REPLACE_OUTPUT)
+ *replace_output = replace_output_;
+ return error_recover_;
+ }
+
+ SdchSourceStream::Delegate::ErrorRecover OnDecodingError(
+ SdchSourceStream* source,
+ 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;
+ }
+
+ protected:
+ scoped_refptr<IOBufferWithSize> out_buffer_;
+
+ private:
+ // Owned by |sdch_source_|.
+ MockSourceStream* source_;
+ std::unique_ptr<SdchSourceStream> sdch_source_;
+
+ std::string test_dictionary_id_;
+ std::string test_dictionary_text_;
+
+ std::string last_get_dictionary_id_;
+ bool dictionary_id_error_handled_;
+ bool get_dictionary_error_handled_;
+ bool decoding_error_handled_;
+ SdchSourceStream::Delegate::ErrorRecover error_recover_;
+ std::string replace_output_;
+};
+
+INSTANTIATE_TEST_CASE_P(SdchSourceStreamTests,
+ SdchSourceStreamTest,
+ ::testing::Values(MockSourceStream::SYNC,
+ MockSourceStream::ASYNC));
+
+TEST_P(SdchSourceStreamTest, EmptyStream) {
+ 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};
+ mock_source()->AddReadResult(id, sizeof(id), OK, GetParam());
+ SetTestDictionary(id, "...");
+ SetErrorRecover(SdchSourceStream::Delegate::PASS_THROUGH, std::string());
+ TestCompletionCallback callback;
+ int result = ReadStream(callback);
+ result = CompleteReadIfAsync(result, &callback, mock_source());
+
+ EXPECT_TRUE(dictionary_id_error_handled());
+ EXPECT_EQ("", 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 ErrorRecover NONE.
+TEST_P(SdchSourceStreamTest, BogusDictionaryIdNoRecover) {
+ char id[] = {0x1f, '0', '0', '0', '0', '0', '0', '0', 0x0};
+ mock_source()->AddReadResult(id, sizeof(id), OK, GetParam());
+ SetTestDictionary(id, "...");
+ SetErrorRecover(SdchSourceStream::Delegate::NONE, std::string());
+ TestCompletionCallback callback;
+ int result = ReadStream(callback);
+ result = CompleteReadIfAsync(result, &callback, mock_source());
+
+ EXPECT_TRUE(dictionary_id_error_handled());
+ EXPECT_EQ("", 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";
+ SetErrorRecover(SdchSourceStream::Delegate::PASS_THROUGH, std::string());
+ 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(get_dictionary_error_handled());
+ EXPECT_EQ(id, 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);
+ 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(get_dictionary_error_handled());
+ EXPECT_EQ(server_id, 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);
+ 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(decoding_error_handled());
+ EXPECT_EQ(server_id, 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) {
+ out_buffer_ = new IOBufferWithSize(kSmallBufferSize);
+ std::string response;
+ std::string server_id;
+ AppendDictionaryIdTo(&response, &server_id);
+ 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(decoding_error_handled());
+ EXPECT_EQ(server_id, 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);
+ 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(decoding_error_handled());
+ EXPECT_EQ(server_id, 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);
+ 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(decoding_error_handled());
+ EXPECT_EQ(server_id, 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) {
+ {
+ std::string sdch_response;
+ std::string server_id;
+ AppendDictionaryIdTo(&sdch_response, &server_id);
+ 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());
+
+ std::unique_ptr<GzipSourceStream> gzip_source = GzipSourceStream::Create(
+ base::WrapUnique(source), SourceStream::TYPE_GZIP);
+ std::unique_ptr<SdchSourceStream> sdch_source(
+ new SdchSourceStream(std::move(gzip_source), this));
+ TestCompletionCallback callback;
+ int result =
+ sdch_source->Read(out_buffer(), out_buffer_size(), callback.callback());
+ result = CompleteReadIfAsync(result, &callback, source);
+ EXPECT_FALSE(decoding_error_handled());
+ EXPECT_EQ(server_id, last_get_dictionary_id());
+ EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), result);
+ EXPECT_EQ(0, memcmp(kTestData, out_data(), result));
+ EXPECT_EQ("GZIP,SDCH", sdch_source->Description());
+ }
+
+ {
+ // Try a small output buffer
+ out_buffer_ = new IOBufferWithSize(kSmallBufferSize);
+ std::string sdch_response;
+ std::string server_id;
+ AppendDictionaryIdTo(&sdch_response, &server_id);
+ 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<SdchSourceStream> sdch_source(
+ new SdchSourceStream(std::move(gzip_source), this));
+ 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);
+ EXPECT_GE(kSmallBufferSize, static_cast<size_t>(rv));
+ actual_output.append(out_data(), rv);
+ }
+ EXPECT_FALSE(decoding_error_handled());
+ EXPECT_EQ(server_id, last_get_dictionary_id());
+ EXPECT_EQ(sizeof(kTestData) - 1, actual_output.size());
+ EXPECT_EQ(kTestData, actual_output);
+ EXPECT_EQ("GZIP,SDCH", sdch_source->Description());
+ }
+}
+
Randy Smith (Not in Mondays) 2016/10/04 20:06:30 Should we have a DecompressWithSmallOutputBuffer t
xunjieli 2016/10/05 13:44:57 On line 344? TEST_P(SdchSourceStreamTest, Decompre
+} // namespace net

Powered by Google App Engine
This is Rietveld 408576698