| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/chunked_upload_data_stream.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/base/upload_data_stream.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/test/gtest_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| namespace net { |
| |
| namespace { |
| |
| constexpr char kTestData[] = "0123456789"; |
| constexpr size_t kTestDataSize = std::size(kTestData) - 1; |
| constexpr size_t kTestBufferSize = 1 << 14; // 16KB. |
| |
| } // namespace |
| |
| // Reads data once from the upload data stream, and returns the data as string. |
| // Expects the read to succeed synchronously. |
| std::string ReadSync(UploadDataStream* stream, int buffer_size) { |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(buffer_size); |
| int result = stream->Read(buf.get(), |
| buffer_size, |
| TestCompletionCallback().callback()); |
| EXPECT_GE(result, 0); |
| return std::string(buf->data(), result); |
| } |
| |
| // Check the case data is added after the first read attempt. |
| TEST(ChunkedUploadDataStreamTest, AppendOnce) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| TestCompletionCallback callback; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kTestBufferSize); |
| int result = stream.Read(buf.get(), kTestBufferSize, callback.callback()); |
| ASSERT_THAT(result, IsError(ERR_IO_PENDING)); |
| |
| stream.AppendData(kTestData, kTestDataSize, true); |
| int read = callback.WaitForResult(); |
| ASSERT_GE(read, 0); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read)); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, AppendOnceBeforeRead) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| stream.AppendData(kTestData, kTestDataSize, true); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, AppendOnceBeforeInit) { |
| ChunkedUploadDataStream stream(0); |
| |
| stream.AppendData(kTestData, kTestDataSize, true); |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, MultipleAppends) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| TestCompletionCallback callback; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kTestBufferSize); |
| for (size_t i = 0; i < kTestDataSize; ++i) { |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(i, stream.position()); |
| ASSERT_FALSE(stream.IsEOF()); |
| int bytes_read = stream.Read(buf.get(), |
| kTestBufferSize, |
| callback.callback()); |
| ASSERT_THAT(bytes_read, IsError(ERR_IO_PENDING)); |
| stream.AppendData(&kTestData[i], 1, i == kTestDataSize - 1); |
| ASSERT_EQ(1, callback.WaitForResult()); |
| EXPECT_EQ(kTestData[i], buf->data()[0]); |
| } |
| |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, MultipleAppendsBetweenReads) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kTestBufferSize); |
| for (size_t i = 0; i < kTestDataSize; ++i) { |
| EXPECT_EQ(i, stream.position()); |
| ASSERT_FALSE(stream.IsEOF()); |
| stream.AppendData(&kTestData[i], 1, i == kTestDataSize - 1); |
| int bytes_read = stream.Read(buf.get(), |
| kTestBufferSize, |
| TestCompletionCallback().callback()); |
| ASSERT_EQ(1, bytes_read); |
| EXPECT_EQ(kTestData[i], buf->data()[0]); |
| } |
| |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| } |
| |
| // Checks that multiple reads can be merged. |
| TEST(ChunkedUploadDataStreamTest, MultipleAppendsBeforeInit) { |
| ChunkedUploadDataStream stream(0); |
| stream.AppendData(kTestData, 1, false); |
| stream.AppendData(kTestData + 1, 1, false); |
| stream.AppendData(kTestData + 2, kTestDataSize - 2, true); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, MultipleReads) { |
| // Use a read size different from the write size to test bounds checking. |
| const size_t kReadSize = kTestDataSize + 3; |
| |
| ChunkedUploadDataStream stream(0); |
| stream.AppendData(kTestData, kTestDataSize, false); |
| stream.AppendData(kTestData, kTestDataSize, false); |
| stream.AppendData(kTestData, kTestDataSize, false); |
| stream.AppendData(kTestData, kTestDataSize, true); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kReadSize); |
| EXPECT_EQ("0123456789012", data); |
| EXPECT_EQ(kReadSize, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| data = ReadSync(&stream, kReadSize); |
| EXPECT_EQ("3456789012345", data); |
| EXPECT_EQ(2 * kReadSize, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| data = ReadSync(&stream, kReadSize); |
| EXPECT_EQ("6789012345678", data); |
| EXPECT_EQ(3 * kReadSize, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| data = ReadSync(&stream, kReadSize); |
| EXPECT_EQ("9", data); |
| EXPECT_EQ(4 * kTestDataSize, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, EmptyUpload) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| TestCompletionCallback callback; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kTestBufferSize); |
| int result = stream.Read(buf.get(), kTestBufferSize, callback.callback()); |
| ASSERT_THAT(result, IsError(ERR_IO_PENDING)); |
| |
| stream.AppendData(nullptr, 0, true); |
| int read = callback.WaitForResult(); |
| EXPECT_EQ(0, read); |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, EmptyUploadEndedBeforeInit) { |
| ChunkedUploadDataStream stream(0); |
| stream.AppendData(nullptr, 0, true); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| ASSERT_EQ("", data); |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, RewindAfterComplete) { |
| ChunkedUploadDataStream stream(0); |
| stream.AppendData(kTestData, 1, false); |
| stream.AppendData(kTestData + 1, kTestDataSize - 1, true); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| |
| // Rewind stream and repeat. |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| } |
| |
| TEST(ChunkedUploadDataStreamTest, RewindWhileReading) { |
| ChunkedUploadDataStream stream(0); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| TestCompletionCallback callback; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kTestBufferSize); |
| int result = stream.Read(buf.get(), kTestBufferSize, callback.callback()); |
| ASSERT_THAT(result, IsError(ERR_IO_PENDING)); |
| |
| ASSERT_THAT( |
| stream.Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| EXPECT_FALSE(stream.IsInMemory()); |
| EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data. |
| EXPECT_EQ(0u, stream.position()); |
| EXPECT_FALSE(stream.IsEOF()); |
| |
| // Adding data now should not result in calling the original read callback, |
| // since the stream was re-initialized for reuse, which cancels all pending |
| // reads. |
| stream.AppendData(kTestData, kTestDataSize, true); |
| EXPECT_FALSE(callback.have_result()); |
| |
| std::string data = ReadSync(&stream, kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| EXPECT_EQ(kTestDataSize, stream.position()); |
| ASSERT_TRUE(stream.IsEOF()); |
| EXPECT_FALSE(callback.have_result()); |
| } |
| |
| // Check the behavior of ChunkedUploadDataStream::Writer. |
| TEST(ChunkedUploadDataStreamTest, ChunkedUploadDataStreamWriter) { |
| auto stream = std::make_unique<ChunkedUploadDataStream>(0); |
| std::unique_ptr<ChunkedUploadDataStream::Writer> writer( |
| stream->CreateWriter()); |
| |
| // Write before Init. |
| ASSERT_TRUE(writer->AppendData(kTestData, 1, false)); |
| ASSERT_THAT( |
| stream->Init(TestCompletionCallback().callback(), NetLogWithSource()), |
| IsOk()); |
| |
| // Write after Init. |
| ASSERT_TRUE(writer->AppendData(kTestData + 1, kTestDataSize - 1, false)); |
| |
| TestCompletionCallback callback; |
| std::string data = ReadSync(stream.get(), kTestBufferSize); |
| EXPECT_EQ(kTestData, data); |
| |
| // Writing data should gracefully fail if the stream is deleted while still |
| // appending data to it. |
| stream.reset(); |
| EXPECT_FALSE(writer->AppendData(kTestData, kTestDataSize, true)); |
| } |
| |
| } // namespace net |