| // Copyright 2021 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 "services/network/web_bundle_chunked_buffer.h" |
| |
| #include "base/check.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace network { |
| namespace { |
| constexpr unsigned char kNumeric10Chars[] = "0123456789"; |
| constexpr unsigned char kSmallAlphabet10Chars[] = "abcdefghij"; |
| constexpr unsigned char kLargeAlphabet10Chars[] = "ABCDEFGHIJ"; |
| } // namespace |
| |
| class WebBundleChunkedBufferTest : public ::testing::Test { |
| protected: |
| using Chunk = WebBundleChunkedBuffer::Chunk; |
| using ChunkVector = WebBundleChunkedBuffer::ChunkVector; |
| |
| struct ReadTestCase { |
| uint64_t offset; |
| uint64_t length; |
| bool expected_contains_all; |
| uint64_t expected_available_length; |
| std::string expected_read_result; |
| }; |
| void RunBasicReadTest(const WebBundleChunkedBuffer& buffer, |
| const ReadTestCase& test_case) { |
| SCOPED_TRACE(::testing::Message() << "offset: " << test_case.offset |
| << " length: " << test_case.length); |
| EXPECT_EQ(test_case.expected_contains_all, |
| buffer.ContainsAll(test_case.offset, test_case.length)); |
| EXPECT_EQ(test_case.expected_available_length, |
| buffer.GetAvailableLength(test_case.offset, test_case.length)); |
| auto data = std::vector<unsigned char>(test_case.length, 0); |
| EXPECT_EQ(test_case.expected_available_length, |
| buffer.ReadData(test_case.offset, test_case.length, data.data())); |
| EXPECT_EQ(std::string(data.begin(), |
| data.begin() + test_case.expected_available_length), |
| test_case.expected_read_result); |
| |
| if (test_case.expected_available_length > 0) { |
| data = std::vector<unsigned char>(test_case.expected_available_length, 0); |
| auto data_source = |
| buffer.CreateDataSource(test_case.offset, test_case.length); |
| EXPECT_EQ(test_case.expected_available_length, data_source->GetLength()); |
| auto result = data_source->Read( |
| 0, |
| base::span<char>(reinterpret_cast<char*>(data.data()), data.size())); |
| EXPECT_EQ(MOJO_RESULT_OK, result.result); |
| EXPECT_EQ(test_case.expected_available_length, result.bytes_read); |
| EXPECT_EQ(std::string(data.begin(), |
| data.begin() + test_case.expected_available_length), |
| test_case.expected_read_result); |
| } |
| } |
| }; |
| |
| TEST_F(WebBundleChunkedBufferTest, Chunk) { |
| constexpr unsigned char kData[] = "Hello World!"; |
| constexpr size_t kDataLength = sizeof(kData); |
| auto data = base::MakeRefCounted<base::RefCountedBytes>(kData, kDataLength); |
| uint64_t start_pos = 10; |
| Chunk chunk = Chunk(start_pos, std::move(data)); |
| EXPECT_EQ(start_pos, chunk.start_pos()); |
| EXPECT_EQ(start_pos + kDataLength, chunk.end_pos()); |
| EXPECT_EQ(kDataLength, chunk.size()); |
| EXPECT_EQ(0, memcmp(chunk.data(), kData, kDataLength)); |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, EmptyBuffer) { |
| WebBundleChunkedBuffer buffer; |
| EXPECT_TRUE(buffer.empty()); |
| EXPECT_TRUE(buffer.ContainsAll(0, 0)); |
| EXPECT_TRUE(buffer.ContainsAll(10, 0)); |
| EXPECT_FALSE(buffer.ContainsAll(0, 10)); |
| EXPECT_FALSE(buffer.ContainsAll(10, 10)); |
| EXPECT_EQ(0ull, buffer.GetAvailableLength(0, 0)); |
| EXPECT_EQ(0ull, buffer.GetAvailableLength(0, 10)); |
| EXPECT_EQ(0ull, buffer.GetAvailableLength(10, 10)); |
| EXPECT_TRUE(buffer.CreateDataSource(0, 0) == nullptr); |
| EXPECT_TRUE(buffer.CreateDataSource(0, 10) == nullptr); |
| EXPECT_TRUE(buffer.CreateDataSource(10, 10) == nullptr); |
| std::vector<unsigned char> data(10, 0); |
| EXPECT_FALSE(buffer.ReadData(0, 10, data.data())); |
| EXPECT_FALSE(buffer.ReadData(10, 10, data.data())); |
| |
| // Appendding 0 byte doesn't do anything. |
| buffer.Append(kNumeric10Chars, 0); |
| EXPECT_TRUE(buffer.empty()); |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, ReadTest_OneChunk) { |
| WebBundleChunkedBuffer buffer; |
| buffer.Append(kNumeric10Chars, 10); |
| |
| ReadTestCase test_cases[] = { |
| {0, 0, true, 0, ""}, |
| {0, 10, true, 10, "0123456789"}, |
| {0, 11, false, 10, "0123456789"}, |
| {1, 0, true, 0, ""}, |
| {1, 9, true, 9, "123456789"}, |
| {1, 10, false, 9, "123456789"}, |
| {5, 5, true, 5, "56789"}, |
| {5, 6, false, 5, "56789"}, |
| {9, 1, true, 1, "9"}, |
| {9, 2, false, 1, "9"}, |
| {10, 1, false, 0, ""}, |
| }; |
| |
| for (const auto& test_case : test_cases) { |
| RunBasicReadTest(buffer, test_case); |
| } |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, ReadTest_MultipleChunks) { |
| WebBundleChunkedBuffer buffer; |
| buffer.Append(kNumeric10Chars, 10); |
| buffer.Append(kSmallAlphabet10Chars, 10); |
| buffer.Append(kLargeAlphabet10Chars, 10); |
| |
| ReadTestCase test_cases1[] = { |
| {0, 0, true, 0, ""}, |
| {0, 10, true, 10, "0123456789"}, |
| {0, 11, true, 11, "0123456789a"}, |
| {0, 20, true, 20, "0123456789abcdefghij"}, |
| {0, 21, true, 21, "0123456789abcdefghijA"}, |
| {0, 30, true, 30, "0123456789abcdefghijABCDEFGHIJ"}, |
| {0, 31, false, 30, "0123456789abcdefghijABCDEFGHIJ"}, |
| |
| {1, 0, true, 0, ""}, |
| {1, 9, true, 9, "123456789"}, |
| {1, 10, true, 10, "123456789a"}, |
| {1, 19, true, 19, "123456789abcdefghij"}, |
| {1, 20, true, 20, "123456789abcdefghijA"}, |
| {1, 29, true, 29, "123456789abcdefghijABCDEFGHIJ"}, |
| {1, 30, false, 29, "123456789abcdefghijABCDEFGHIJ"}, |
| |
| {13, 3, true, 3, "def"}, |
| {13, 10, true, 10, "defghijABC"}, |
| {13, 30, false, 17, "defghijABCDEFGHIJ"}, |
| |
| {29, 10, false, 1, "J"}, |
| {30, 10, false, 0, ""}, |
| }; |
| |
| for (const auto& test_case : test_cases1) { |
| RunBasicReadTest(buffer, test_case); |
| } |
| |
| // Append many chunks to trigger the binary search logic in FindChunk. |
| for (size_t i = 0; i < 50; ++i) { |
| buffer.Append(kNumeric10Chars, 10); |
| buffer.Append(kSmallAlphabet10Chars, 10); |
| buffer.Append(kLargeAlphabet10Chars, 10); |
| } |
| |
| ReadTestCase test_cases2[] = { |
| {30 * 3 + 5, 30, true, 30, "56789abcdefghijABCDEFGHIJ01234"}, |
| {30 * 9 + 8, 20, true, 20, "89abcdefghijABCDEFGH"}, |
| }; |
| for (const auto& test_case : test_cases2) { |
| RunBasicReadTest(buffer, test_case); |
| } |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, PartialBuffer) { |
| WebBundleChunkedBuffer buffer; |
| |
| buffer.Append(kNumeric10Chars, 10); |
| buffer.Append(kSmallAlphabet10Chars, 10); |
| buffer.Append(kLargeAlphabet10Chars, 10); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~~ |
| auto partial = buffer.CreatePartialBuffer(1, 5); |
| EXPECT_EQ(1u, partial->chunks_.size()); |
| EXPECT_EQ(0ull, partial->chunks_[0].start_pos()); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~ |
| partial = buffer.CreatePartialBuffer(5, 5); |
| EXPECT_EQ(1u, partial->chunks_.size()); |
| EXPECT_EQ(0ull, partial->chunks_[0].start_pos()); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~ ~ |
| partial = buffer.CreatePartialBuffer(5, 6); |
| EXPECT_EQ(2u, partial->chunks_.size()); |
| EXPECT_EQ(0ull, partial->chunks_[0].start_pos()); |
| EXPECT_EQ(10ull, partial->chunks_[1].start_pos()); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~~~~ |
| partial = buffer.CreatePartialBuffer(12, 8); |
| EXPECT_EQ(1u, partial->chunks_.size()); |
| EXPECT_EQ(10ull, partial->chunks_[0].start_pos()); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~~~~ ~ |
| partial = buffer.CreatePartialBuffer(12, 9); |
| EXPECT_EQ(2u, partial->chunks_.size()); |
| EXPECT_EQ(10ull, partial->chunks_[0].start_pos()); |
| EXPECT_EQ(20ull, partial->chunks_[1].start_pos()); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ |
| // ~~~~~~~~ ~~~~~~~ |
| partial = buffer.CreatePartialBuffer(12, 15); |
| EXPECT_EQ(2u, partial->chunks_.size()); |
| EXPECT_EQ(10ull, partial->chunks_[0].start_pos()); |
| EXPECT_EQ(20ull, partial->chunks_[1].start_pos()); |
| ReadTestCase test_cases[] = { |
| {9, 10, false, 0, ""}, |
| {10, 10, true, 10, "abcdefghij"}, |
| {10, 11, true, 11, "abcdefghijA"}, |
| {10, 19, true, 19, "abcdefghijABCDEFGHI"}, |
| {10, 20, true, 20, "abcdefghijABCDEFGHIJ"}, |
| {10, 21, false, 20, "abcdefghijABCDEFGHIJ"}, |
| }; |
| for (const auto& test_case : test_cases) { |
| RunBasicReadTest(*partial, test_case); |
| } |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, FindChunk) { |
| WebBundleChunkedBuffer buffer; |
| // FindChunk() always returns chunks_.end() when empty. |
| EXPECT_TRUE(buffer.FindChunk(0) == buffer.chunks_.end()); |
| EXPECT_TRUE(buffer.FindChunk(1) == buffer.chunks_.end()); |
| |
| // Append many chunks to trigger the binary search logic in FindChunk. |
| for (size_t i = 0; i < 50; ++i) { |
| buffer.Append(kNumeric10Chars, 10); |
| } |
| for (int i = 0; i < 510; ++i) { |
| SCOPED_TRACE(::testing::Message() << "i: " << i); |
| EXPECT_EQ(i / 10, buffer.FindChunk(i) - buffer.chunks_.begin()); |
| } |
| EXPECT_TRUE(buffer.FindChunk(510) == buffer.chunks_.end()); |
| |
| // 0123456789 0123456789 0123456789 012... |
| // ~~~~~ ~~~~~ |
| auto partial = buffer.CreatePartialBuffer(15, 10); |
| EXPECT_TRUE(partial->FindChunk(9) == partial->chunks_.end()); |
| EXPECT_TRUE(partial->FindChunk(10) == partial->chunks_.begin()); |
| EXPECT_TRUE(partial->FindChunk(19) == partial->chunks_.begin()); |
| EXPECT_TRUE(partial->FindChunk(20) == partial->chunks_.begin() + 1); |
| EXPECT_TRUE(partial->FindChunk(29) == partial->chunks_.begin() + 1); |
| EXPECT_TRUE(partial->FindChunk(30) == partial->chunks_.end()); |
| } |
| |
| TEST_F(WebBundleChunkedBufferTest, DataSource) { |
| WebBundleChunkedBuffer buffer; |
| |
| buffer.Append(kNumeric10Chars, 10); |
| buffer.Append(kSmallAlphabet10Chars, 10); |
| buffer.Append(kLargeAlphabet10Chars, 10); |
| buffer.Append(kNumeric10Chars, 10); |
| |
| // 0123456789 abcdefghij ABCDEFGHIJ 0123456789 |
| // ~~~~~ ~~ |
| auto data_source = buffer.CreateDataSource(15, 7); |
| ASSERT_TRUE(data_source); |
| EXPECT_EQ(7ull, data_source->GetLength()); |
| |
| struct TestCase { |
| uint64_t offset; |
| uint64_t length; |
| MojoResult expected_result; |
| uint64_t expected_bytes_read; |
| std::string expected_read_result; |
| } test_cases[] = { |
| {0, 6, MOJO_RESULT_OK, 6, "fghijAXXXX"}, |
| {0, 7, MOJO_RESULT_OK, 7, "fghijABXXX"}, |
| {0, 8, MOJO_RESULT_OK, 7, "fghijABXXX"}, |
| {3, 4, MOJO_RESULT_OK, 4, "ijABXXXXXX"}, |
| {3, 5, MOJO_RESULT_OK, 4, "ijABXXXXXX"}, |
| {6, 10, MOJO_RESULT_OK, 1, "BXXXXXXXXX"}, |
| {7, 10, MOJO_RESULT_OUT_OF_RANGE, 0, "XXXXXXXXXX"}, |
| {8, 10, MOJO_RESULT_OUT_OF_RANGE, 0, "XXXXXXXXXX"}, |
| }; |
| for (const auto& test_case : test_cases) { |
| SCOPED_TRACE(::testing::Message() << "offset: " << test_case.offset |
| << " length: " << test_case.length); |
| auto data = std::vector<unsigned char>(10, 'X'); |
| auto result = data_source->Read( |
| test_case.offset, base::span<char>(reinterpret_cast<char*>(data.data()), |
| test_case.length)); |
| EXPECT_EQ(test_case.expected_result, result.result); |
| EXPECT_EQ(test_case.expected_bytes_read, result.bytes_read); |
| EXPECT_EQ(test_case.expected_read_result, |
| std::string(data.begin(), data.begin() + 10)); |
| } |
| } |
| |
| } // namespace network |