• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 // Testing strategy:  For each type of I/O (array, string, file, etc.) we
9 // create an output stream and write some data to it, then create a
10 // corresponding input stream to read the same data back and expect it to
11 // match.  When the data is written, it is written in several small chunks
12 // of varying sizes, with a BackUp() after each chunk.  It is read back
13 // similarly, but with chunks separated at different points.  The whole
14 // process is run with a variety of block sizes for both the input and
15 // the output.
16 
17 #include <gtest/gtest.h>
18 #include "upb/base/status.hpp"
19 #include "upb/io/chunked_input_stream.h"
20 #include "upb/io/chunked_output_stream.h"
21 #include "upb/mem/arena.hpp"
22 
23 namespace upb {
24 namespace {
25 
26 class IoTest : public testing::Test {
27  protected:
28   // Test helpers.
29 
30   // Helper to write an array of data to an output stream.
31   bool WriteToOutput(upb_ZeroCopyOutputStream* output, const void* data,
32                      int size);
33   // Helper to read a fixed-length array of data from an input stream.
34   int ReadFromInput(upb_ZeroCopyInputStream* input, void* data, int size);
35   // Write a string to the output stream.
36   void WriteString(upb_ZeroCopyOutputStream* output, const std::string& str);
37   // Read a number of bytes equal to the size of the given string and checks
38   // that it matches the string.
39   void ReadString(upb_ZeroCopyInputStream* input, const std::string& str);
40   // Writes some text to the output stream in a particular order.  Returns
41   // the number of bytes written, in case the caller needs that to set up an
42   // input stream.
43   int WriteStuff(upb_ZeroCopyOutputStream* output);
44   // Reads text from an input stream and expects it to match what
45   // WriteStuff() writes.
46   void ReadStuff(upb_ZeroCopyInputStream* input, bool read_eof = true);
47 
48   static const int kBlockSizes[];
49   static const int kBlockSizeCount;
50 };
51 
52 const int IoTest::kBlockSizes[] = {1, 2, 5, 7, 10, 23, 64};
53 const int IoTest::kBlockSizeCount = sizeof(IoTest::kBlockSizes) / sizeof(int);
54 
WriteToOutput(upb_ZeroCopyOutputStream * output,const void * data,int size)55 bool IoTest::WriteToOutput(upb_ZeroCopyOutputStream* output, const void* data,
56                            int size) {
57   const uint8_t* in = reinterpret_cast<const uint8_t*>(data);
58   size_t in_size = size;
59   size_t out_size;
60 
61   while (true) {
62     upb::Status status;
63     void* out = upb_ZeroCopyOutputStream_Next(output, &out_size, status.ptr());
64     if (out_size == 0) return false;
65 
66     if (in_size <= out_size) {
67       memcpy(out, in, in_size);
68       upb_ZeroCopyOutputStream_BackUp(output, out_size - in_size);
69       return true;
70     }
71 
72     memcpy(out, in, out_size);
73     in += out_size;
74     in_size -= out_size;
75   }
76 }
77 
ReadFromInput(upb_ZeroCopyInputStream * input,void * data,int size)78 int IoTest::ReadFromInput(upb_ZeroCopyInputStream* input, void* data,
79                           int size) {
80   uint8_t* out = reinterpret_cast<uint8_t*>(data);
81   size_t out_size = size;
82 
83   const void* in;
84   size_t in_size = 0;
85 
86   while (true) {
87     upb::Status status;
88     in = upb_ZeroCopyInputStream_Next(input, &in_size, status.ptr());
89 
90     if (in_size == 0) {
91       return size - out_size;
92     }
93 
94     if (out_size <= in_size) {
95       memcpy(out, in, out_size);
96       if (in_size > out_size) {
97         upb_ZeroCopyInputStream_BackUp(input, in_size - out_size);
98       }
99       return size;  // Copied all of it.
100     }
101 
102     memcpy(out, in, in_size);
103     out += in_size;
104     out_size -= in_size;
105   }
106 }
107 
WriteString(upb_ZeroCopyOutputStream * output,const std::string & str)108 void IoTest::WriteString(upb_ZeroCopyOutputStream* output,
109                          const std::string& str) {
110   EXPECT_TRUE(WriteToOutput(output, str.c_str(), str.size()));
111 }
112 
ReadString(upb_ZeroCopyInputStream * input,const std::string & str)113 void IoTest::ReadString(upb_ZeroCopyInputStream* input,
114                         const std::string& str) {
115   std::unique_ptr<char[]> buffer(new char[str.size() + 1]);
116   buffer[str.size()] = '\0';
117   EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size());
118   EXPECT_STREQ(str.c_str(), buffer.get());
119 }
120 
WriteStuff(upb_ZeroCopyOutputStream * output)121 int IoTest::WriteStuff(upb_ZeroCopyOutputStream* output) {
122   WriteString(output, "Hello world!\n");
123   WriteString(output, "Some te");
124   WriteString(output, "xt.  Blah blah.");
125   WriteString(output, "abcdefg");
126   WriteString(output, "01234567890123456789");
127   WriteString(output, "foobar");
128 
129   const int result = upb_ZeroCopyOutputStream_ByteCount(output);
130   EXPECT_EQ(result, 68);
131   return result;
132 }
133 
134 // Reads text from an input stream and expects it to match what WriteStuff()
135 // writes.
ReadStuff(upb_ZeroCopyInputStream * input,bool read_eof)136 void IoTest::ReadStuff(upb_ZeroCopyInputStream* input, bool read_eof) {
137   ReadString(input, "Hello world!\n");
138   ReadString(input, "Some text.  ");
139   ReadString(input, "Blah ");
140   ReadString(input, "blah.");
141   ReadString(input, "abcdefg");
142   EXPECT_TRUE(upb_ZeroCopyInputStream_Skip(input, 20));
143   ReadString(input, "foo");
144   ReadString(input, "bar");
145 
146   EXPECT_EQ(upb_ZeroCopyInputStream_ByteCount(input), 68);
147 
148   if (read_eof) {
149     uint8_t byte;
150     EXPECT_EQ(ReadFromInput(input, &byte, 1), 0);
151   }
152 }
153 
154 // ===================================================================
155 
TEST_F(IoTest,ArrayIo)156 TEST_F(IoTest, ArrayIo) {
157   const int kBufferSize = 256;
158   uint8_t buffer[kBufferSize];
159 
160   upb::Arena arena;
161   for (int i = 0; i < kBlockSizeCount; i++) {
162     for (int j = 0; j < kBlockSizeCount; j++) {
163       auto output = upb_ChunkedOutputStream_New(buffer, kBufferSize,
164                                                 kBlockSizes[j], arena.ptr());
165       int size = WriteStuff(output);
166       auto input =
167           upb_ChunkedInputStream_New(buffer, size, kBlockSizes[j], arena.ptr());
168       ReadStuff(input);
169     }
170   }
171 }
172 
TEST(ChunkedStream,SingleInput)173 TEST(ChunkedStream, SingleInput) {
174   const int kBufferSize = 256;
175   uint8_t buffer[kBufferSize];
176   upb::Arena arena;
177   auto input =
178       upb_ChunkedInputStream_New(buffer, kBufferSize, kBufferSize, arena.ptr());
179   const void* data;
180   size_t size;
181 
182   upb::Status status;
183   data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr());
184   EXPECT_EQ(size, kBufferSize);
185 
186   data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr());
187   EXPECT_EQ(data, nullptr);
188   EXPECT_EQ(size, 0);
189   EXPECT_TRUE(upb_Status_IsOk(status.ptr()));
190 }
191 
TEST(ChunkedStream,SingleOutput)192 TEST(ChunkedStream, SingleOutput) {
193   const int kBufferSize = 256;
194   uint8_t buffer[kBufferSize];
195   upb::Arena arena;
196   auto output = upb_ChunkedOutputStream_New(buffer, kBufferSize, kBufferSize,
197                                             arena.ptr());
198   size_t size;
199   upb::Status status;
200   void* data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr());
201   EXPECT_EQ(size, kBufferSize);
202 
203   data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr());
204   EXPECT_EQ(data, nullptr);
205   EXPECT_EQ(size, 0);
206   EXPECT_TRUE(upb_Status_IsOk(status.ptr()));
207 }
208 
209 // Check that a zero-size input array doesn't confuse the code.
TEST(ChunkedStream,InputEOF)210 TEST(ChunkedStream, InputEOF) {
211   upb::Arena arena;
212   char buf;
213   auto input = upb_ChunkedInputStream_New(&buf, 0, 1, arena.ptr());
214   size_t size;
215   upb::Status status;
216   const void* data = upb_ZeroCopyInputStream_Next(input, &size, status.ptr());
217   EXPECT_EQ(data, nullptr);
218   EXPECT_EQ(size, 0);
219   EXPECT_TRUE(upb_Status_IsOk(status.ptr()));
220 }
221 
222 // Check that a zero-size output array doesn't confuse the code.
TEST(ChunkedStream,OutputEOF)223 TEST(ChunkedStream, OutputEOF) {
224   upb::Arena arena;
225   char buf;
226   auto output = upb_ChunkedOutputStream_New(&buf, 0, 1, arena.ptr());
227   size_t size;
228   upb::Status status;
229   void* data = upb_ZeroCopyOutputStream_Next(output, &size, status.ptr());
230   EXPECT_EQ(data, nullptr);
231   EXPECT_EQ(size, 0);
232   EXPECT_TRUE(upb_Status_IsOk(status.ptr()));
233 }
234 
235 }  // namespace
236 }  // namespace upb
237