|  | // Protocol Buffers - Google's data interchange format | 
|  | // Copyright 2023 Google LLC.  All rights reserved. | 
|  | // | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://developers.google.com/open-source/licenses/bsd | 
|  |  | 
|  | // Testing strategy:  For each type of I/O (array, string, file, etc.) we | 
|  | // create an output stream and write some data to it, then create a | 
|  | // corresponding input stream to read the same data back and expect it to | 
|  | // match.  When the data is written, it is written in several small chunks | 
|  | // of varying sizes, with a BackUp() after each chunk.  It is read back | 
|  | // similarly, but with chunks separated at different points.  The whole | 
|  | // process is run with a variety of block sizes for both the input and | 
|  | // the output. | 
|  |  | 
|  | #include <gtest/gtest.h> | 
|  | #include "upb/base/status.hpp" | 
|  | #include "upb/io/chunked_input_stream.h" | 
|  | #include "upb/io/chunked_output_stream.h" | 
|  | #include "upb/mem/arena.hpp" | 
|  |  | 
|  | namespace upb { | 
|  | namespace { | 
|  |  | 
|  | class IoTest : public testing::Test { | 
|  | protected: | 
|  | // Test helpers. | 
|  |  | 
|  | // Helper to write an array of data to an output stream. | 
|  | bool WriteToOutput(upb_ZeroCopyOutputStream* output, const void* data, | 
|  | int size); | 
|  | // Helper to read a fixed-length array of data from an input stream. | 
|  | int ReadFromInput(upb_ZeroCopyInputStream* input, void* data, int size); | 
|  | // Write a string to the output stream. | 
|  | void WriteString(upb_ZeroCopyOutputStream* output, const std::string& str); | 
|  | // Read a number of bytes equal to the size of the given string and checks | 
|  | // that it matches the string. | 
|  | void ReadString(upb_ZeroCopyInputStream* input, const std::string& str); | 
|  | // Writes some text to the output stream in a particular order.  Returns | 
|  | // the number of bytes written, in case the caller needs that to set up an | 
|  | // input stream. | 
|  | int WriteStuff(upb_ZeroCopyOutputStream* output); | 
|  | // Reads text from an input stream and expects it to match what | 
|  | // WriteStuff() writes. | 
|  | void ReadStuff(upb_ZeroCopyInputStream* input, bool read_eof = true); | 
|  |  | 
|  | // Similar to WriteStuff, but performs more sophisticated testing. | 
|  | int WriteStuffLarge(upb_ZeroCopyOutputStream* output); | 
|  | // Reads and tests a stream that should have been written to | 
|  | // via WriteStuffLarge(). | 
|  | void ReadStuffLarge(upb_ZeroCopyInputStream* input); | 
|  |  | 
|  | static const int kBlockSizes[]; | 
|  | static const int kBlockSizeCount; | 
|  | }; | 
|  |  | 
|  | const int IoTest::kBlockSizes[] = {1, 2, 5, 7, 10, 23, 64}; | 
|  | const int IoTest::kBlockSizeCount = sizeof(IoTest::kBlockSizes) / sizeof(int); | 
|  |  | 
|  | bool IoTest::WriteToOutput(upb_ZeroCopyOutputStream* output, const void* data, | 
|  | int size) { | 
|  | const uint8_t* in = reinterpret_cast<const uint8_t*>(data); | 
|  | size_t in_size = size; | 
|  | size_t out_size; | 
|  |  | 
|  | while (true) { | 
|  | upb::Status status; | 
|  | void* out = upb_ZeroCopyOutputStream_Next(output, &out_size, status.ptr()); | 
|  | if (out_size == 0) return false; | 
|  |  | 
|  | if (in_size <= out_size) { | 
|  | memcpy(out, in, in_size); | 
|  | upb_ZeroCopyOutputStream_BackUp(output, out_size - in_size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | memcpy(out, in, out_size); | 
|  | in += out_size; | 
|  | in_size -= out_size; | 
|  | } | 
|  | } | 
|  |  | 
|  | int IoTest::ReadFromInput(upb_ZeroCopyInputStream* input, void* data, | 
|  | int size) { | 
|  | uint8_t* out = reinterpret_cast<uint8_t*>(data); | 
|  | size_t out_size = size; | 
|  |  | 
|  | const void* in; | 
|  | size_t in_size = 0; | 
|  |  | 
|  | while (true) { | 
|  | upb::Status status; | 
|  | in = upb_ZeroCopyInputStream_Next(input, &in_size, status.ptr()); | 
|  |  | 
|  | if (in_size == 0) { | 
|  | return size - out_size; | 
|  | } | 
|  |  | 
|  | if (out_size <= in_size) { | 
|  | memcpy(out, in, out_size); | 
|  | if (in_size > out_size) { | 
|  | upb_ZeroCopyInputStream_BackUp(input, in_size - out_size); | 
|  | } | 
|  | return size;  // Copied all of it. | 
|  | } | 
|  |  | 
|  | memcpy(out, in, in_size); | 
|  | out += in_size; | 
|  | out_size -= in_size; | 
|  | } | 
|  | } | 
|  |  | 
|  | void IoTest::WriteString(upb_ZeroCopyOutputStream* output, | 
|  | const std::string& str) { | 
|  | EXPECT_TRUE(WriteToOutput(output, str.c_str(), str.size())); | 
|  | } | 
|  |  | 
|  | void IoTest::ReadString(upb_ZeroCopyInputStream* input, | 
|  | const std::string& str) { | 
|  | std::unique_ptr<char[]> buffer(new char[str.size() + 1]); | 
|  | buffer[str.size()] = '\0'; | 
|  | EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size()); | 
|  | EXPECT_STREQ(str.c_str(), buffer.get()); | 
|  | } | 
|  |  | 
|  | int IoTest::WriteStuff(upb_ZeroCopyOutputStream* output) { | 
|  | WriteString(output, "Hello world!\n"); | 
|  | WriteString(output, "Some te"); | 
|  | WriteString(output, "xt.  Blah blah."); | 
|  | WriteString(output, "abcdefg"); | 
|  | WriteString(output, "01234567890123456789"); | 
|  | WriteString(output, "foobar"); | 
|  |  | 
|  | const int result = upb_ZeroCopyOutputStream_ByteCount(output); | 
|  | EXPECT_EQ(result, 68); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Reads text from an input stream and expects it to match what WriteStuff() | 
|  | // writes. | 
|  | void IoTest::ReadStuff(upb_ZeroCopyInputStream* input, bool read_eof) { | 
|  | ReadString(input, "Hello world!\n"); | 
|  | ReadString(input, "Some text.  "); | 
|  | ReadString(input, "Blah "); | 
|  | ReadString(input, "blah."); | 
|  | ReadString(input, "abcdefg"); | 
|  | EXPECT_TRUE(upb_ZeroCopyInputStream_Skip(input, 20)); | 
|  | ReadString(input, "foo"); | 
|  | ReadString(input, "bar"); | 
|  |  | 
|  | EXPECT_EQ(upb_ZeroCopyInputStream_ByteCount(input), 68); | 
|  |  | 
|  | if (read_eof) { | 
|  | uint8_t byte; | 
|  | EXPECT_EQ(ReadFromInput(input, &byte, 1), 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | int IoTest::WriteStuffLarge(upb_ZeroCopyOutputStream* output) { | 
|  | WriteString(output, "Hello world!\n"); | 
|  | WriteString(output, "Some te"); | 
|  | WriteString(output, "xt.  Blah blah."); | 
|  | WriteString(output, std::string(100000, 'x'));  // A very long string | 
|  | WriteString(output, std::string(100000, 'y'));  // A very long string | 
|  | WriteString(output, "01234567890123456789"); | 
|  |  | 
|  | const int result = upb_ZeroCopyOutputStream_ByteCount(output); | 
|  | EXPECT_EQ(result, 200055); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Reads text from an input stream and expects it to match what WriteStuff() | 
|  | // writes. | 
|  | void IoTest::ReadStuffLarge(upb_ZeroCopyInputStream* input) { | 
|  | ReadString(input, "Hello world!\nSome text.  "); | 
|  | EXPECT_TRUE(upb_ZeroCopyInputStream_Skip(input, 5)); | 
|  | ReadString(input, "blah."); | 
|  | EXPECT_TRUE(upb_ZeroCopyInputStream_Skip(input, 100000 - 10)); | 
|  | ReadString(input, std::string(10, 'x') + std::string(100000 - 20000, 'y')); | 
|  | EXPECT_TRUE(upb_ZeroCopyInputStream_Skip(input, 20000 - 10)); | 
|  | ReadString(input, "yyyyyyyyyy01234567890123456789"); | 
|  | EXPECT_EQ(upb_ZeroCopyInputStream_ByteCount(input), 200055); | 
|  |  | 
|  | uint8_t byte; | 
|  | EXPECT_EQ(ReadFromInput(input, &byte, 1), 0); | 
|  | } | 
|  |  | 
|  | // =================================================================== | 
|  |  | 
|  | TEST_F(IoTest, ArrayIo) { | 
|  | const int kBufferSize = 256; | 
|  | uint8_t buffer[kBufferSize]; | 
|  |  | 
|  | upb::Arena arena; | 
|  | for (int i = 0; i < kBlockSizeCount; i++) { | 
|  | for (int j = 0; j < kBlockSizeCount; j++) { | 
|  | auto output = upb_ChunkedOutputStream_New(buffer, kBufferSize, | 
|  | kBlockSizes[j], arena.ptr()); | 
|  | int size = WriteStuff(output); | 
|  | auto input = | 
|  | upb_ChunkedInputStream_New(buffer, size, kBlockSizes[j], arena.ptr()); | 
|  | ReadStuff(input); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(ChunkedStream, SingleInput) { | 
|  | const int kBufferSize = 256; | 
|  | uint8_t buffer[kBufferSize]; | 
|  | upb::Arena arena; | 
|  | auto input = | 
|  | upb_ChunkedInputStream_New(buffer, kBufferSize, kBufferSize, arena.ptr()); | 
|  | const void* data; | 
|  | size_t size; | 
|  |  | 
|  | upb::Status status; | 
|  | data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr()); | 
|  | EXPECT_EQ(size, kBufferSize); | 
|  |  | 
|  | data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr()); | 
|  | EXPECT_EQ(data, nullptr); | 
|  | EXPECT_EQ(size, 0); | 
|  | EXPECT_TRUE(upb_Status_IsOk(status.ptr())); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedStream, SingleOutput) { | 
|  | const int kBufferSize = 256; | 
|  | uint8_t buffer[kBufferSize]; | 
|  | upb::Arena arena; | 
|  | auto output = upb_ChunkedOutputStream_New(buffer, kBufferSize, kBufferSize, | 
|  | arena.ptr()); | 
|  | size_t size; | 
|  | upb::Status status; | 
|  | void* data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr()); | 
|  | EXPECT_EQ(size, kBufferSize); | 
|  |  | 
|  | data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr()); | 
|  | EXPECT_EQ(data, nullptr); | 
|  | EXPECT_EQ(size, 0); | 
|  | EXPECT_TRUE(upb_Status_IsOk(status.ptr())); | 
|  | } | 
|  |  | 
|  | // Check that a zero-size input array doesn't confuse the code. | 
|  | TEST(ChunkedStream, InputEOF) { | 
|  | upb::Arena arena; | 
|  | char buf; | 
|  | auto input = upb_ChunkedInputStream_New(&buf, 0, 1, arena.ptr()); | 
|  | size_t size; | 
|  | upb::Status status; | 
|  | const void* data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr()); | 
|  | EXPECT_EQ(data, nullptr); | 
|  | EXPECT_EQ(size, 0); | 
|  | EXPECT_TRUE(upb_Status_IsOk(status.ptr())); | 
|  | } | 
|  |  | 
|  | // Check that a zero-size output array doesn't confuse the code. | 
|  | TEST(ChunkedStream, OutputEOF) { | 
|  | upb::Arena arena; | 
|  | char buf; | 
|  | auto output = upb_ChunkedOutputStream_New(&buf, 0, 1, arena.ptr()); | 
|  | size_t size; | 
|  | upb::Status status; | 
|  | void* data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr()); | 
|  | EXPECT_EQ(data, nullptr); | 
|  | EXPECT_EQ(size, 0); | 
|  | EXPECT_TRUE(upb_Status_IsOk(status.ptr())); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace upb |